Repository: NebulousCloud/helix Branch: master Commit: 7303003d2c92 Files: 197 Total size: 1.5 MB Directory structure: gitextract_omsmarjc/ ├── .editorconfig ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .luacheckrc ├── LICENSE.txt ├── README.md ├── config.ld ├── docs/ │ ├── css/ │ │ ├── highlight.css │ │ └── ldoc.css │ ├── hooks/ │ │ ├── class.lua │ │ ├── faction.lua │ │ └── plugin.lua │ ├── js/ │ │ └── app.js │ ├── manual/ │ │ ├── converting-from-clockwork.md │ │ └── getting-started.md │ └── templates/ │ ├── landing.ltp │ ├── ldoc.ltp │ ├── module.ltp │ └── sidebar.ltp ├── entities/ │ ├── entities/ │ │ ├── ix_item.lua │ │ ├── ix_money.lua │ │ └── ix_shipment.lua │ └── weapons/ │ └── ix_hands.lua ├── gamemode/ │ ├── cl_init.lua │ ├── config/ │ │ ├── sh_config.lua │ │ └── sh_options.lua │ ├── core/ │ │ ├── cl_skin.lua │ │ ├── derma/ │ │ │ ├── cl_attribute.lua │ │ │ ├── cl_bar.lua │ │ │ ├── cl_business.lua │ │ │ ├── cl_character.lua │ │ │ ├── cl_charcreate.lua │ │ │ ├── cl_charload.lua │ │ │ ├── cl_classes.lua │ │ │ ├── cl_config.lua │ │ │ ├── cl_credits.lua │ │ │ ├── cl_deathscreen.lua │ │ │ ├── cl_dev_icon.lua │ │ │ ├── cl_entitymenu.lua │ │ │ ├── cl_generic.lua │ │ │ ├── cl_help.lua │ │ │ ├── cl_information.lua │ │ │ ├── cl_intro.lua │ │ │ ├── cl_inventory.lua │ │ │ ├── cl_menu.lua │ │ │ ├── cl_menubutton.lua │ │ │ ├── cl_modelpanel.lua │ │ │ ├── cl_notice.lua │ │ │ ├── cl_noticebar.lua │ │ │ ├── cl_overrides.lua │ │ │ ├── cl_scoreboard.lua │ │ │ ├── cl_settings.lua │ │ │ ├── cl_shipment.lua │ │ │ ├── cl_spawnicon.lua │ │ │ ├── cl_storage.lua │ │ │ ├── cl_subpanel.lua │ │ │ └── cl_tooltip.lua │ │ ├── hooks/ │ │ │ ├── cl_hooks.lua │ │ │ ├── sh_hooks.lua │ │ │ └── sv_hooks.lua │ │ ├── libs/ │ │ │ ├── cl_bar.lua │ │ │ ├── cl_hud.lua │ │ │ ├── cl_markup.lua │ │ │ ├── cl_networking.lua │ │ │ ├── sh_animation.lua │ │ │ ├── sh_anims.lua │ │ │ ├── sh_attribs.lua │ │ │ ├── sh_business.lua │ │ │ ├── sh_character.lua │ │ │ ├── sh_chatbox.lua │ │ │ ├── sh_class.lua │ │ │ ├── sh_command.lua │ │ │ ├── sh_currency.lua │ │ │ ├── sh_date.lua │ │ │ ├── sh_faction.lua │ │ │ ├── sh_flag.lua │ │ │ ├── sh_inventory.lua │ │ │ ├── sh_item.lua │ │ │ ├── sh_language.lua │ │ │ ├── sh_log.lua │ │ │ ├── sh_menu.lua │ │ │ ├── sh_notice.lua │ │ │ ├── sh_option.lua │ │ │ ├── sh_player.lua │ │ │ ├── sh_plugin.lua │ │ │ ├── sh_storage.lua │ │ │ ├── sv_database.lua │ │ │ ├── sv_networking.lua │ │ │ ├── sv_player.lua │ │ │ └── thirdparty/ │ │ │ ├── cl_ikon.lua │ │ │ ├── data/ │ │ │ │ └── sh_utf8_casemap.lua │ │ │ ├── sh_cami.lua │ │ │ ├── sh_date.lua │ │ │ ├── sh_middleclass.lua │ │ │ ├── sh_pon.lua │ │ │ ├── sh_tween.lua │ │ │ ├── sh_utf8.lua │ │ │ ├── sh_yaml.lua │ │ │ └── sv_mysql.lua │ │ ├── meta/ │ │ │ ├── sh_character.lua │ │ │ ├── sh_entity.lua │ │ │ ├── sh_inventory.lua │ │ │ ├── sh_item.lua │ │ │ ├── sh_player.lua │ │ │ └── sh_tool.lua │ │ ├── sh_commands.lua │ │ ├── sh_config.lua │ │ ├── sh_data.lua │ │ └── sh_util.lua │ ├── init.lua │ ├── items/ │ │ ├── ammo/ │ │ │ ├── sh_357ammo.txt │ │ │ ├── sh_ar2ammo.txt │ │ │ ├── sh_crossbowammo.txt │ │ │ ├── sh_pistolammo.txt │ │ │ ├── sh_rocketammo.txt │ │ │ ├── sh_shotgunammo.txt │ │ │ └── sh_smg1ammo.txt │ │ ├── bags/ │ │ │ ├── sh_large.txt │ │ │ └── sh_small.txt │ │ ├── base/ │ │ │ ├── sh_ammo.lua │ │ │ ├── sh_bags.lua │ │ │ ├── sh_outfit.lua │ │ │ ├── sh_pacoutfit.lua │ │ │ └── sh_weapons.lua │ │ ├── pacoutfit/ │ │ │ └── sh_skullmask.txt │ │ ├── sh_defaultitem.txt │ │ └── weapons/ │ │ ├── sh_357.txt │ │ ├── sh_ar2.txt │ │ ├── sh_crowbar.txt │ │ ├── sh_pistol.txt │ │ └── sh_smg1.txt │ ├── languages/ │ │ ├── sh_dutch.lua │ │ ├── sh_english.lua │ │ ├── sh_french.lua │ │ ├── sh_german.lua │ │ ├── sh_korean.lua │ │ ├── sh_norwegian.lua │ │ ├── sh_polish.lua │ │ ├── sh_portuguese.lua │ │ ├── sh_russian.lua │ │ └── sh_spanish.lua │ └── shared.lua ├── helix.example.yml ├── helix.txt └── plugins/ ├── 3dpanel.lua ├── 3dtext.lua ├── act/ │ ├── cl_hooks.lua │ ├── sh_definitions.lua │ ├── sh_plugin.lua │ └── sv_hooks.lua ├── ammosave.lua ├── area/ │ ├── cl_hooks.lua │ ├── cl_plugin.lua │ ├── derma/ │ │ ├── cl_area.lua │ │ └── cl_areaedit.lua │ ├── languages/ │ │ ├── sh_english.lua │ │ └── sh_russian.lua │ ├── sh_plugin.lua │ ├── sv_hooks.lua │ └── sv_plugin.lua ├── chatbox/ │ ├── derma/ │ │ ├── cl_chatbox.lua │ │ └── cl_chatboxcustomize.lua │ └── sh_plugin.lua ├── containers/ │ ├── entities/ │ │ └── entities/ │ │ └── ix_container.lua │ ├── sh_definitions.lua │ └── sh_plugin.lua ├── crosshair.lua ├── doors/ │ ├── cl_plugin.lua │ ├── derma/ │ │ └── cl_door.lua │ ├── entities/ │ │ └── weapons/ │ │ └── ix_keys.lua │ ├── sh_commands.lua │ ├── sh_plugin.lua │ └── sv_plugin.lua ├── logging.lua ├── mapscene.lua ├── observer.lua ├── pac.lua ├── permakill.lua ├── persistence.lua ├── propprotect.lua ├── recognition.lua ├── saveitems.lua ├── spawns.lua ├── spawnsaver.lua ├── stamina/ │ ├── attributes/ │ │ ├── sh_end.lua │ │ └── sh_stm.lua │ └── sh_plugin.lua ├── strength/ │ ├── attributes/ │ │ └── sh_str.lua │ └── sh_plugin.lua ├── thirdperson.lua ├── typing.lua ├── vendor/ │ ├── derma/ │ │ ├── cl_vendor.lua │ │ ├── cl_vendoreditor.lua │ │ └── cl_vendorfaction.lua │ ├── entities/ │ │ └── entities/ │ │ └── ix_vendor.lua │ └── sh_plugin.lua └── wepselect.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = crlf insert_final_newline = true charset = utf-8 indent_style = tab indent_size = 4 [.travis.yml] indent_style = space indent_size = 2 ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, pull_request] jobs: linter: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: path: helix - uses: leafo/gh-actions-lua@v8.0.0 with: luaVersion: "5.2" - uses: leafo/gh-actions-luarocks@v4.0.0 - name: Pull gluacheck uses: actions/checkout@v2 with: repository: impulsh/gluacheck path: luacheck - name: Build gluacheck working-directory: luacheck run: luarocks make - name: Lint working-directory: helix run: luacheck . docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: path: helix - uses: leafo/gh-actions-lua@v8.0.0 with: luaVersion: "5.2" - uses: leafo/gh-actions-luarocks@v4.0.0 - name: Pull LDoc uses: actions/checkout@v2 with: repository: impulsh/LDoc path: ldoc - name: Build LDoc working-directory: ldoc run: luarocks make - name: Build docs working-directory: helix run: ldoc . --fatalwarnings - name: Copy assets working-directory: helix run: | cp -v docs/css/* docs/html cp -v docs/js/* docs/html - name: Deploy if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'NebulousCloud/helix' && success() uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: helix/docs/html cname: docs.gethelix.co ================================================ FILE: .gitignore ================================================ *.sublime-* gamemode/config/sv_database.lua docs/html .vscode/* .DS_Store helix.yml ================================================ FILE: .luacheckrc ================================================ max_line_length = 128 std = "luajit+gmod+helix" ignore = { "212", -- unused argument } -- helix stds.helix = {} stds.helix.globals = { "ix", "Schema", "ITEM", "PLUGIN", "ATTRIBUTE", "NAME", "LANGUAGE", "FACTION", "CLASS", "CHAT_RECOGNIZED", "ALWAYS_RAISED", "ICON_RENDER_QUEUE", "USABLE_FUNCS", } stds.helix.read_globals = { "L", "L2", "IX_RELOADED", "CHAT_CLASS", "HOOKS_CACHE", "BAR_HEIGHT", "ACCESS_LABELS", "CAMI", "netstream", "mysql", "pon", "ikon", "BaseClass", "SetNetVar", "GetNetVar", "ixSoundDuration", "HOLDTYPE_TRANSLATOR", "PLAYER_HOLDTYPE_TRANSLATOR", "ACT_VM_FISTS_DRAW", "ACT_VM_FISTS_HOLSTER", "TOOLTIP_GENERIC", "TOOLTIP_ITEM", "FLAG_NORMAL", "FLAG_SUCCESS", "FLAG_WARNING", "FLAG_DANGER", "FLAG_SERVER", "FLAG_DEV", "DOOR_OWNER", "DOOR_TENANT", "DOOR_GUEST", "DOOR_NONE", "VENDOR_BUY", "VENDOR_SELL", "VENDOR_BOTH", "VENDOR_WELCOME", "VENDOR_LEAVE", "VENDOR_NOTRADE", "VENDOR_PRICE", "VENDOR_STOCK", "VENDOR_MODE", "VENDOR_MAXSTOCK", "VENDOR_SELLANDBUY", "VENDOR_SELLONLY", "VENDOR_BUYONLY", "VENDOR_TEXT", "FCAP_IMPULSE_USE", "FCAP_CONTINUOUS_USE", "FCAP_ONOFF_USE", "FCAP_DIRECTIONAL_USE", "FCAP_USE_ONGROUND", "FCAP_USE_IN_RADIUS", } files = { -- some phrases are unavoidably long, so we'll ignore the max line length for language files ["gamemode/languages/**/*.lua"] = { ignore = { "631" } }, ["plugins/**/languages/*.lua"] = { ignore = { "631" } } } -- ignore third party files exclude_files = { "gamemode/core/libs/thirdparty/**/*.lua" } -- gmod stds.gmod = {} stds.gmod.globals = { "GM", "ENT", "TOOL", "SWEP" } stds.gmod.read_globals = { "VERSION", "CLIENT", "SERVER", "GAMEMODE", "NULL", "vector_origin", "vector_up", "angle_zero", "color_white", "color_black", "color_transparent", "PLAYERANIMEVENT_CANCEL_RELOAD", "ACT_COMBINE_THROW_GRENADE", -- Generated on Wed Jan 17 02:56:57 2018 "ACT_MP_GESTURE_VC_NODYES", "ACT_MELEE_ATTACK_SWING_GESTURE", "SCHED_TAKE_COVER_FROM_ORIGIN", "ACT_BUSY_SIT_GROUND_ENTRY", "ACT_DOD_CROUCH_IDLE_C96", "ACT_IDLE_STEALTH", "ACT_DOD_PRIMARYATTACK_DEPLOYED", "ACT_MP_STAND_PRIMARY", "ACT_DOD_CROUCHWALK_AIM", "ACT_DOD_RELOAD_PRONE_DEPLOYED_BAR", "ACT_GLOCK_SHOOT_RELOAD", "TEXT_ALIGN_CENTER", "DMG_CRUSH", "ScrH", "ACT_DOD_RELOAD_CROUCH_BAR", "ACT_HL2MP_JUMP_PASSIVE", "ACT_MP_GESTURE_VC_HANDMOUTH_SECONDARY", "ACT_MP_JUMP_START_MELEE", "ACT_GET_UP_CROUCH", "ACT_DOD_CROUCHWALK_IDLE_GREASE", "ACT_DOD_CROUCHWALK_IDLE_PISTOL", "ACT_HL2MP_WALK_CROUCH_SUITCASE", "ACT_HL2MP_GESTURE_RELOAD_SLAM", "SIMPLE_USE", "ACT_DOD_WALK_AIM_GREN_STICK", "ACT_DOD_PRONEWALK_IDLE_MG", "FL_FLY", "ACT_HL2MP_IDLE_CROUCH_REVOLVER", "presets", "ACT_MP_RUN_MELEE", "halo", "HULL_WIDE_SHORT", "video", "SND_SPAWNING", "ACT_MP_GRENADE2_DRAW", "ACT_OVERLAY_PRIMARYATTACK", "COND_SEE_NEMESIS", "KEY_O", "ACT_OVERLAY_GRENADEREADY", "ACT_COVER_MED", "AddConsoleCommand", "ACT_VM_IIDLE_M203", "ACT_HL2MP_JUMP_RPG", "ACT_DOD_STAND_AIM_MG", "MAT_VENT", "ACT_VM_HITRIGHT2", "ACT_VM_RELOAD_EMPTY", "DSprite", "BONE_CALCULATE_MASK", "GetGlobalVector", "ACT_HL2MP_IDLE_PISTOL", "SetGlobalVector", "HTTP", "WorldToLocal", "ACT_MP_CROUCH_IDLE", "COLLISION_GROUP_VEHICLE", "KEY_XBUTTON_B", "ACT_PICKUP_RACK", "CAP_MOVE_CRAWL", "ACT_DOD_CROUCHWALK_AIM_BOLT", "ACT_MP_MELEE_GRENADE2_DRAW", "ACT_DIE_BARNACLE_SWALLOW", "ACT_WALK_STEALTH_PISTOL", "ACT_DOD_PRONE_AIM_MP44", "ACT_PRONE_FORWARD", "COND_ENEMY_OCCLUDED", "ACT_DOD_RUN_IDLE_BOLT", "GetConVarNumber", "ACT_DOD_CROUCHWALK_AIM_RIFLE", "ACT_DOD_RELOAD_RIFLEGRENADE", "ACT_VM_IIN_M203", "ACT_MP_ATTACK_AIRWALK_BUILDING", "FVPHYSICS_DMG_DISSOLVE", "ACT_INVALID", "ACT_DOD_RELOAD_PRONE_MP44", "ACT_SLAM_TRIPMINE_DRAW", "ACT_MP_GESTURE_VC_NODNO", "SetGlobalAngle", "ACT_DOD_SPRINT_IDLE_BOLT", "ACT_MP_PRIMARY_GRENADE1_IDLE", "ACT_VM_PRIMARYATTACK_DEPLOYED_1", "ClientsideScene", "MASK_SHOT", "DTree_Node_Button", "GetGlobalAngle", "MOVETYPE_ISOMETRIC", "ACT_GESTURE_RANGE_ATTACK_SMG1_LOW", "NAV_MESH_STOP", "COND_NONE", "ACT_SHIELD_ATTACK", "ACT_GESTURE_RANGE_ATTACK_SMG2", "CNavArea", "ACT_HL2MP_WALK", "include", "ACT_WALK_ANGRY", "ACT_DOD_PRIMARYATTACK_PRONE_PISTOL", "ACT_MP_GESTURE_VC_FINGERPOINT_BUILDING", "DDrawer", "ACT_VM_IRECOIL1", "BOX_TOP", "ACT_MP_RUN_PDA", "ACT_DOD_RELOAD_BOLT", "SaveLastMap", "ACT_DOD_RUN_ZOOM_BOLT", "ACT_MP_RELOAD_AIRWALK_LOOP", "ACT_DEEPIDLE1", "COND_SEE_ENEMY", "DColorPalette", "DMG_DIRECT", "Derma_Message", "DPanelSelect", "ACT_HL2MP_ZOMBIE_SLUMP_RISE", "FVPHYSICS_NO_IMPACT_DMG", "CLASS_CONSCRIPT", "CONTINUOUS_USE", "STUDIO_TRANSPARENCY", "NOTIFY_UNDO", "ACT_MP_ATTACK_CROUCH_GRENADE_PRIMARY", "ACT_SMG2_FIRE2", "ACT_RANGE_ATTACK_AR1", "ACT_MP_DEPLOYED_IDLE", "ACT_MP_ATTACK_SWIM_PRIMARYFIRE", "kRenderFxEnvSnow", "COND_WAY_CLEAR", "MATERIAL_LINE_STRIP", "SetGlobalString", "ACT_IDLE_RELAXED", "ACT_VM_PRIMARYATTACK", "ACT_HL2MP_GESTURE_RANGE_ATTACK_DUEL", "ACT_VM_DEPLOY_EMPTY", "ACT_VM_IDLE_EMPTY", "Matrix", "ACT_DOD_SECONDARYATTACK_PRONE_TOMMY", "COND_WEAPON_SIGHT_OCCLUDED", "ACT_DOD_PRIMARYATTACK_KNIFE", "ACT_VM_RELOAD_END_EMPTY", "menu", "CLASS_SCANNER", "ACT_MP_CROUCHWALK_SECONDARY", "ACT_DOD_SECONDARYATTACK_BOLT", "SF_NPC_ALWAYSTHINK", "ACT_DOD_WALK_IDLE_TNT", "ACT_HL2MP_WALK_ZOMBIE", "ACT_IDLE_SMG1_RELAXED", "TextEntry", "ACT_DOD_STAND_ZOOM_RIFLE", "COND_HEAR_BUGBAIT", "COND_WEAPON_PLAYER_NEAR_TARGET", "LoadPresets", "IN_ALT2", "ACT_MP_SWIM_DEPLOYED", "SetGlobalInt", "DrawSunbeams", "ACT_MP_ATTACK_SWIM_SECONDARY", "KEY_4", "ACT_MP_GESTURE_VC_FISTPUMP_MELEE", "ACT_DOD_RUN_IDLE_PSCHRECK", "ACT_PICKUP_GROUND", "ACT_WALK_RPG", "ACT_BUSY_SIT_CHAIR", "ACT_DOD_CROUCHWALK_AIM_KNIFE", "TYPE_COLOR", "ACT_DOD_CROUCH_IDLE_30CAL", "ACT_DOD_PRONEWALK_AIM_SPADE", "properties", "ACT_HL2MP_IDLE_RPG", "achievements", "CAP_WEAPON_MELEE_ATTACK2", "ACT_VM_UNLOAD", "ACT_VM_DRAW_EMPTY", "LocalPlayer", "ACT_ZOMBIE_CLIMB_UP", "SURF_WARP", "MAT_SLOSH", "ACT_DOD_WALK_ZOOM_PSCHRECK", "FSOLID_TRIGGER_TOUCH_DEBRIS", "ACT_MP_JUMP_LAND_MELEE", "PLAYERANIMEVENT_SWIM", "ACT_DOD_RELOAD_PSCHRECK", "ACT_DOD_RELOAD_CROUCH", "PLAYER", "ACT_DOD_RELOAD_CROUCH_MP44", "table", "ACT_HL2MP_FIST_BLOCK", "ACT_VM_PRIMARYATTACK_EMPTY", "ACT_RANGE_AIM_PISTOL_LOW", "Panel", "BONE_USED_BY_VERTEX_MASK", "ACT_MP_ATTACK_CROUCH_PRIMARY", "KEY_PAD_8", "ACT_CROUCHING_SHIELD_UP", "ipairs", "KEY_LALT", "ACT_DIEVIOLENT", "ACT_DOD_PRIMARYATTACK_MP40", "KEY_PAD_PLUS", "KEY_LBRACKET", "MsgAll", "ACT_DOD_CROUCHWALK_IDLE_MP44", "GetGlobalInt", "ACT_CROUCHING_SHIELD_DOWN", "NUM_BEAMS", "COND_CAN_MELEE_ATTACK1", "COND_LOST_ENEMY", "ACT_RUN_AIM_RIFLE", "ACT_VM_PULLBACK_HIGH", "DrawBackground", "TEXT_ALIGN_BOTTOM", "ACT_HL2MP_WALK_AR2", "ACT_RUN_PROTECTED", "CreateParticleSystem", "ACT_HL2MP_JUMP_DUEL", "ACT_DOD_CROUCHWALK_AIM_BAR", "ACT_VM_HOLSTERFULL_M203", "ACT_DOD_PRONEWALK_IDLE_PSCHRECK", "ACT_DOD_PRIMARYATTACK_PRONE_MP44", "ACT_STARTDYING", "TYPE_BOOL", "MOVETYPE_NOCLIP", "FVPHYSICS_PENETRATING", "NPC_STATE_INVALID", "STUDIO_TWOPASS", "ACT_VM_MISSRIGHT2", "KEY_E", "FORCE_STRING", "HITGROUP_LEFTLEG", "ACT_GET_DOWN_STAND", "ACT_HL2MP_SWIM_REVOLVER", "ACT_DOD_PRONE_ZOOM_FORWARD_RIFLE", "ACT_DOD_PRONEWALK_IDLE_MP40", "ACT_MP_GESTURE_VC_NODNO_SECONDARY", "BLEND_ZERO", "spawnmenu", "D_NU", "KEY_PAD_0", "ACT_MP_RUN_SECONDARY", "ACT_DIE_BACKSHOT", "ACT_WALK_CROUCH_RIFLE", "ACT_MP_ATTACK_STAND_GRENADE_PRIMARY", "ACT_HL2MP_GESTURE_RELOAD_REVOLVER", "ACT_CROSSBOW_IDLE_UNLOADED", "IMAGE_FORMAT_RGBA16161616", "ACT_DOD_CROUCHWALK_ZOOM_BOLT", "STENCILCOMPARISONFUNCTION_LESS", "ACT_DOD_SPRINT_AIM_KNIFE", "ACT_BARNACLE_PULL", "CONTENTS_TESTFOGVOLUME", "SNDLVL_45dB", "ACT_DIE_LEFTSIDE", "kRenderFxEnvRain", "ACT_GMOD_GESTURE_DISAGREE", "ACT_MP_JUMP_LAND_PRIMARY", "SQLStr", "ACT_OVERLAY_SHIELD_ATTACK", "SCHED_ALERT_WALK", "ACT_SIGNAL_ADVANCE", "DImageButton", "search", "ACT_DOD_RELOAD_CROUCH_RIFLEGRENADE", "KEY_XSTICK1_DOWN", "FVPHYSICS_NO_NPC_IMPACT_DMG", "RENDERGROUP_TRANSLUCENT", "DrawTexturize", "ACT_HANDGRENADE_THROW3", "ACT_OVERLAY_SHIELD_UP", "HITGROUP_CHEST", "BONE_ALWAYS_PROCEDURAL", "ACT_DOD_PRIMARYATTACK_PRONE_SPADE", "ACT_HL2MP_JUMP_SMG1", "ACT_DOD_STAND_AIM_MP40", "CreateMaterial", "COND_TARGET_OCCLUDED", "kRenderFxHologram", "KEY_APOSTROPHE", "RENDERGROUP_STATIC", "ENT_BRUSH", "ACT_VM_DETACH_SILENCER", "KEY_ENTER", "ACT_VM_DEPLOYED_LIFTED_IDLE", "SCHED_MOVE_TO_WEAPON_RANGE", "ACT_MP_RELOAD_AIRWALK_SECONDARY", "ACT_GMOD_GESTURE_RANGE_ZOMBIE_SPECIAL", "KEY_F7", "SetClipboardText", "ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE2", "ACT_MP_WALK_MELEE", "DMG_ACID", "ACT_MP_RELOAD_AIRWALK_PRIMARY_LOOP", "DProperty_Generic", "PANEL", "ProjectedTexture", "ACT_MP_RELOAD_SWIM_SECONDARY_END", "ACT_VM_IDLE_DEPLOYED_4", "ACT_IDLE_MANNEDGUN", "ACT_DOD_RELOAD_K43", "ACT_DOD_HS_CROUCH_PSCHRECK", "Add_NPC_Class", "TEXTUREFLAGS_UNUSED_00080000", "COND_HEAR_WORLD", "ACT_VM_PRIMARYATTACK_8", "MOVECOLLIDE_DEFAULT", "ACT_DOD_WALK_IDLE_GREASE", "ACT_WALK_SUITCASE", "FCVAR_GAMEDLL", "ACT_PLAYER_CROUCH_FIRE", "ACT_MP_ATTACK_AIRWALK_GRENADE_BUILDING", "ACT_HL2MP_GESTURE_RELOAD_PISTOL", "ACT_RANGE_ATTACK_SMG1_LOW", "ACT_RELOAD_SMG1_LOW", "ACT_FLINCH_RIGHTLEG", "ACT_MP_PRIMARY_GRENADE1_ATTACK", "ACT_COVER_SMG1_LOW", "ONOFF_USE", "ACT_HL2MP_JUMP_SLAM", "CheckButton", "kRenderFxStrobeSlow", "TYPE_EFFECTDATA", "FSOLID_VOLUME_CONTENTS", "TimedCos", "ACT_DEEPIDLE3", "ACT_HL2MP_WALK_CROUCH_PASSIVE", "ACT_DUCK_DODGE", "ACT_HL2MP_RUN_ZOMBIE_FAST", "PrintMessage", "usermessage", "ACT_DOD_RUN_AIM", "JoinServer", "ACT_MP_GESTURE_VC_THUMBSUP_PRIMARY", "TYPE_FILE", "RunGameUICommand", "ACT_HL2MP_JUMP_KNIFE", "ACT_HL2MP_WALK_FIST", "MOVECOLLIDE_FLY_BOUNCE", "EFL_NO_WATER_VELOCITY_CHANGE", "SURF_HITBOX", "STENCILOPERATION_REPLACE", "ACT_MP_ATTACK_AIRWALK_MELEE", "ColorToHSV", "ACT_MP_WALK_PDA", "IMAGE_FORMAT_ABGR8888", "BuildNetworkedVarsTable", "KEY_XBUTTON_A", "hook", "SF_CITIZEN_RANDOM_HEAD", "CreateSprite", "DListView_ColumnPlain", "IsTableOfEntitiesValid", "ACT_HL2MP_IDLE_CROUCH_MELEE", "ACT_TURN_RIGHT", "CompileFile", "ACT_DOD_SPRINT_IDLE_RIFLE", "DKillIcon", "SNDLVL_180dB", "ACT_DOD_WALK_IDLE_PSCHRECK", "DMG_DROWN", "DMG_BURN", "ACT_MP_SWIM_BUILDING", "CONTENTS_SOLID", "ACT_MP_STAND_IDLE", "SCHED_SCENE_GENERIC", "PLAYERANIMEVENT_FLINCH_RIGHTLEG", "CreateClientConVar", "DListLayout", "ACT_MP_RELOAD_AIRWALK_SECONDARY_END", "ACT_CROUCH", "ACT_VM_PULLBACK_LOW", "NPC_STATE_IDLE", "ACT_MP_RELOAD_SWIM_END", "ACT_MP_ATTACK_CROUCH_GRENADE_SECONDARY", "COND_HEAR_BULLET_IMPACT", "ACT_OPEN_DOOR", "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED_MG", "ACT_IDLE_ANGRY_PISTOL", "ACT_DOD_STAND_ZOOM_PSCHRECK", "COND_CAN_MELEE_ATTACK2", "ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED", "engine", "SF_PHYSBOX_NEVER_PICK_UP", "ACT_GESTURE_RANGE_ATTACK_ML", "ACT_DOD_DEPLOY_30CAL", "CLASS_ZOMBIE", "ACT_RPG_IDLE_UNLOADED", "DProperty_Int", "ACT_IDLE_SHOTGUN_RELAXED", "RunStringEx", "ACT_MP_JUMP", "MOVETYPE_FLYGRAVITY", "matproxy", "ACT_VM_MISSRIGHT", "physenv", "DColorMixer", "RenderStereoscopy", "Schedule", "ACT_MP_GESTURE_VC_FISTPUMP", "ACT_RELOAD_PISTOL_LOW", "SF_NPC_NO_WEAPON_DROP", "ACT_SLAM_STICKWALL_DRAW", "ACT_HL2MP_WALK_CROUCH_GRENADE", "MsgN", "ACT_DOD_CROUCHWALK_IDLE_PSCHRECK", "CAP_SQUAD", "ACT_MP_AIRWALK_SECONDARY", "ALL_VISIBLE_CONTENTS", "ColorAlpha", "ACT_DOD_SPRINT_IDLE_GREASE", "SNDLVL_STATIC", "killicon", "ACT_SIGNAL2", "DProperty_VectorColor", "DCollapsibleCategory", "FCVAR_ARCHIVE_XBOX", "KEY_PAD_MULTIPLY", "fingerposer", "RENDERGROUP_OPAQUE", "require", "GetGlobalBool", "SCHED_COMBAT_WALK", "ACT_DEEPIDLE2", "ACT_HL2MP_SWIM_IDLE_PASSIVE", "SNDLVL_50dB", "NAV_MESH_FUNC_COST", "rawequal", "ACT_MP_GESTURE_VC_THUMBSUP", "ACT_DOD_RUN_IDLE_30CAL", "ACT_DRIVE_AIRBOAT", "ACT_GMOD_SIT_ROLLERCOASTER", "PrecacheScene", "SetGlobalBool", "IsInGame", "HUD_PRINTNOTIFY", "ACT_DOD_SPRINT_IDLE_MP40", "TYPE_SAVE", "ParticleEffect", "ACT_RUN_AIM_STEALTH", "ACT_DOD_RELOAD_PRONE_C96", "KEY_LEFT", "EFL_DONTWALKON", "PATTACH_ABSORIGIN", "ACT_HL2MP_GESTURE_RELOAD_PHYSGUN", "ACT_HL2MP_SWIM", "CHAN_VOICE_BASE", "ACT_DOD_PRIMARYATTACK_PRONE_GREN_FRAG", "ACT_HL2MP_WALK_CROUCH_ANGRY", "ACT_DOD_RUN_ZOOM_PSCHRECK", "ACT_DOD_PRONEWALK_IDLE_TNT", "TextEntryLoseFocus", "ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1", "IsColor", "ACT_RANGE_ATTACK2", "ACT_DOD_WALK_AIM_GREN_FRAG", "BONE_USED_BY_VERTEX_LOD4", "CONTENTS_TEAM3", "ACT_DOD_WALK_AIM_SPADE", "ACT_DOD_PRIMARYATTACK_PRONE_GREASE", "PresetEditor", "FVPHYSICS_PLAYER_HELD", "SetGlobalEntity", "ACT_HL2MP_SWIM_KNIFE", "ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND", "ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE", "GetSaveFileDetails", "ACT_DOD_RELOAD_PRONE_DEPLOYED_MG", "ACT_RUN_STEALTH", "coroutine", "GetGlobalEntity", "ACT_VM_IDLE_DEPLOYED_EMPTY", "HITGROUP_LEFTARM", "ACT_DOD_RELOAD_PRONE_DEPLOYED_MG34", "TEXFILTER", "SF_PHYSPROP_PREVENT_PICKUP", "ACT_HL2MP_WALK_CROUCH", "DrawToyTown", "DBubbleContainer", "EndTooltip", "MsgC", "SCHED_FLEE_FROM_BEST_SOUND", "ACT_MP_CROUCH_SECONDARY", "ACT_OBJ_UPGRADING", "ACT_DOD_CROUCH_AIM", "SF_NPC_ALTCOLLISION", "ACT_MP_AIRWALK_PRIMARY", "ACT_DOD_STAND_IDLE_TNT", "ACT_MP_GRENADE1_DRAW", "ACT_DOD_RELOAD_PRONE_DEPLOYED_FG42", "ACT_VM_PULLBACK_HIGH_BAKE", "ACT_RANGE_ATTACK_AR2", "SF_NPC_GAG", "UpdateLoadPanel", "CHAN_REPLACE", "ACT_VM_ISHOOT_M203", "ACT_DOD_RELOAD_PRONE_RIFLEGRENADE", "FCVAR_SPONLY", "TRACER_NONE", "DTooltip", "KEY_HOME", "ACT_IDLE_STEALTH_PISTOL", "CAP_MOVE_SHOOT", "Task", "ACT_VM_DRAW_DEPLOYED", "TYPE_CONVAR", "ACT_GMOD_TAUNT_DANCE", "ACT_FLINCH_CHEST", "ACT_DOD_HS_CROUCH_KNIFE", "ACT_DOD_PRIMARYATTACK_DEPLOYED_MG", "JOYSTICK_FIRST", "ACT_MP_SECONDARY_GRENADE2_IDLE", "SCHED_SCRIPTED_FACE", "ACT_DOD_RUN_IDLE_RIFLE", "ACT_MP_ATTACK_STAND_GRENADE_SECONDARY", "ACT_IDLE_RIFLE", "ACT_HL2MP_SWIM_AR2", "KEY_M", "ACT_SLAM_TRIPMINE_IDLE", "ContentIcon", "ACT_VM_PRIMARYATTACK_DEPLOYED", "DrawBloom", "ACT_DOD_CROUCHWALK_ZOOM_RIFLE", "DAlphaBar", "MAT_SNOW", "ACT_VM_IDLE", "ACT_MP_RELOAD_AIRWALK", "ACT_DOD_WALK_ZOOM_RIFLE", "IMAGE_FORMAT_RGB565", "ACT_DOD_PRONE_ZOOMED", "ACT_VM_DRYFIRE", "TEXTUREFLAGS_SINGLECOPY", "ACT_HL2MP_IDLE_CROUCH_MAGIC", "ACT_MP_RELOAD_CROUCH_PRIMARY", "STUDIO_STATIC_LIGHTING", "ACT_DOD_CROUCH_ZOOM_BOLT", "ACT_LOOKBACK_LEFT", "SNDLVL_130dB", "EFL_KEEP_ON_RECREATE_ENTITIES", "ACT_MP_ATTACK_CROUCH_GRENADE", "IN_MOVERIGHT", "EFL_DIRTY_ABSANGVELOCITY", "TRACER_RAIL", "COND_PROVOKED", "PLAYER_WALK", "ACT_MP_ATTACK_STAND_GRENADE_MELEE", "ACT_DOD_HS_CROUCH_BAZOOKA", "ACT_HL2MP_IDLE_CROUCH_FIST", "ACT_DOD_STAND_IDLE", "ACT_DOD_PRIMARYATTACK_GREASE", "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED_RIFLE", "ACT_DIEFORWARD", "FFT_2048", "ACT_DOD_CROUCHWALK_IDLE_BAZOOKA", "SNDLVL_90dB", "DrawMotionBlur", "DPanel", "MAT_METAL", "EF_NOSHADOW", "ACT_MP_SECONDARY_GRENADE1_IDLE", "ACT_HL2MP_JUMP_ZOMBIE", "ACT_VM_UNDEPLOY_8", "COLLISION_GROUP_NONE", "ACT_VM_DEPLOYED_IRON_IN", "ACT_MP_ATTACK_STAND_GRENADE", "RENDERMODE_TRANSALPHADD", "ACT_MP_JUMP_MELEE", "ACT_MP_JUMP_SECONDARY", "Either", "ACT_HL2MP_GESTURE_RANGE_ATTACK_CAMERA", "KEY_W", "SCHED_COMBAT_PATROL", "ACT_FLINCH_HEAD", "ACT_DROP_WEAPON", "NOTIFY_CLEANUP", "DCategoryList", "ACT_SPECIAL_ATTACK1", "ACT_DOD_PRONEWALK_IDLE_BAZOOKA", "ACT_HL2MP_WALK_CROUCH_ZOMBIE_01", "ACT_GAUSS_SPINCYCLE", "D_HT", "FL_CONVEYOR", "ACT_GMOD_TAUNT_MUSCLE", "TYPE_TEXTURE", "DLabelURL", "list", "DMG_NEVERGIB", "ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW", "ACT_DOD_STAND_ZOOM_BAZOOKA", "ACT_DOD_PRONE_DEPLOY_TOMMY", "ACT_DOD_RUN_AIM_30CAL", "ACT_DOD_PRONEWALK_IDLE_BOLT", "ACT_DOD_RELOAD_PRONE_TOMMY", "FL_UNBLOCKABLE_BY_PLAYER", "ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED", "ACT_SLAM_STICKWALL_ATTACH2", "ACT_HL2MP_WALK_ZOMBIE_05", "ACT_DOD_CROUCH_IDLE", "ACT_MP_MELEE_GRENADE2_IDLE", "isfunction", "SetPhysConstraintSystem", "STENCIL_DECR", "ACT_VM_DEPLOY", "KEY_XSTICK2_RIGHT", "ACT_DOD_RUN_IDLE_MP44", "CREATERENDERTARGETFLAGS_HDR", "ACT_BUSY_LEAN_BACK_ENTRY", "MATERIAL_LINES", "Particle", "ACT_DOD_STAND_IDLE_RIFLE", "SCHED_RUN_FROM_ENEMY", "TranslateDownloadableName", "ACT_DOD_RELOAD_DEPLOYED_30CAL", "ACT_DOD_DEFUSE_TNT", "kRenderFxPulseFastWider", "FindMetaTable", "RENDERGROUP_BOTH", "ACT_DOD_WALK_AIM_PSCHRECK", "RandomPairs", "SNDLVL_NORM", "ACT_VM_ISHOOT", "BONE_SCREEN_ALIGN_CYLINDER", "PLAYERANIMEVENT_FLINCH_LEFTARM", "player_manager", "ACT_MP_ATTACK_CROUCH_GRENADE_MELEE", "ACT_VM_IDLE_4", "SF_NPC_FADE_CORPSE", "ACT_DO_NOT_DISTURB", "ACT_VM_DEPLOYED_IRON_OUT", "ACT_RANGE_ATTACK_PISTOL_LOW", "ACT_DOD_HS_IDLE_BAZOOKA", "NPC", "ACT_DOD_STAND_IDLE_MG", "ACT_WALK_CROUCH", "Weapon", "FL_OBJECT", "ACT_MP_AIRWALK_PDA", "ACT_IDLE_AIM_RELAXED", "DHorizontalScroller", "isvector", "ACT_DOD_WALK_AIM_BAZOOKA", "KEY_SLASH", "ACT_RANGE_ATTACK_SMG2", "COND_REPEATED_DAMAGE", "ACT_TRANSITION", "DTab", "MATERIAL_TRIANGLES", "ACT_DOD_CROUCH_IDLE_TNT", "FVPHYSICS_WAS_THROWN", "DNotify", "ACT_MP_ATTACK_STAND_PRIMARY", "ACT_MP_MELEE_GRENADE1_DRAW", "SCHED_ALERT_FACE", "ACT_HL2MP_WALK_CROUCH_SLAM", "KEY_XBUTTON_RIGHT", "ACT_VM_HITCENTER2", "ACT_DOD_CROUCHWALK_IDLE_C96", "ACT_MP_RELOAD_SWIM_LOOP", "KEY_XBUTTON_X", "SNDLVL_75dB", "ACT_DOD_PRIMARYATTACK_PRONE_BOLT", "ACT_MP_RELOAD_STAND_SECONDARY_LOOP", "COND_NO_CUSTOM_INTERRUPTS", "ACT_DOD_IDLE_ZOOMED", "CUserCmd", "BOX_BACK", "ACT_DOD_RUN_IDLE_PISTOL", "ACT_DOD_PRONE_DEPLOYED", "FCVAR_NOTIFY", "KEY_COMMA", "ACT_DOD_WALK_ZOOMED", "ACT_RELOAD_PISTOL", "TYPE_DLIGHT", "CLASS_MILITARY", "ACT_MP_DEPLOYED", "ACT_VM_DEPLOYED_IN", "OBS_MODE_DEATHCAM", "Format", "ACT_DOD_SPRINT_AIM_GREN_STICK", "CAP_WEAPON_RANGE_ATTACK1", "ACT_MP_JUMP_LAND_SECONDARY", "CONTENTS_WATER", "ACT_OBJ_DISMANTLING", "EyePos", "FingerVar", "PathFollower", "ACT_DOD_RELOAD_C96", "IN_BULLRUSH", "DLabelEditable", "ACT_HL2MP_GESTURE_RELOAD_SUITCASE", "CONTENTS_MONSTERCLIP", "MAT_WARPSHIELD", "ACT_HL2MP_GESTURE_RANGE_ATTACK_ANGRY", "ACT_DOD_HS_IDLE_K98", "KEY_PERIOD", "ACT_DOD_SPRINT_IDLE_BAR", "ACT_DOD_CROUCH_AIM_MP44", "ACT_STEP_RIGHT", "ACT_RUNTOIDLE", "TYPE_STRING", "SCHED_BACK_AWAY_FROM_SAVE_POSITION", "ACT_VM_MISSLEFT", "FFT_512", "ACT_DYINGLOOP", "ACT_DOD_CROUCH_AIM_BAR", "frame_blend", "IMAGE_FORMAT_RGBA16161616F", "ValidPanel", "NAV_MESH_WALK", "RememberCursorPosition", "ACT_HL2MP_JUMP_AR2", "MAT_SAND", "KEY_PAD_7", "ACT_DOD_CROUCH_IDLE_MP40", "ACT_IDLE_ON_FIRE", "KEY_J", "ACT_DOD_RUN_AIM_RIFLE", "CLASS_PROTOSNIPER", "RenderSuperDoF", "kRenderFxRagdoll", "CAP_USE_SHOT_REGULATOR", "ACT_SLAM_TRIPMINE_TO_STICKWALL_ND", "ACT_HL2MP_GESTURE_RELOAD_MELEE", "ACT_PLAYER_WALK_FIRE", "SortedPairsByValue", "ACT_GESTURE_MELEE_ATTACK1", "SCHED_VICTORY_DANCE", "ACT_MP_SWIM_SECONDARY", "DScrollBarGrip", "ClearBackgroundImages", "ACT_GESTURE_TURN_RIGHT90", "KEY_SEMICOLON", "CAP_SIMPLE_RADIUS_DAMAGE", "ACT_DOD_STAND_IDLE_C96", "ACT_SHIELD_KNOCKBACK", "ACT_HL2MP_SWIM_IDLE", "ACT_RUN_RPG_RELAXED", "ACT_HL2MP_SWIM_IDLE_MELEE", "ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM", "ACT_GMOD_GESTURE_POINT", "ACT_DOD_PRONE_DEPLOY_MG", "ACT_RELOAD_SHOTGUN_LOW", "ACT_IDLE_SHOTGUN_AGITATED", "DModelSelectMulti", "ACT_VM_IOUT", "STENCILCOMPARISONFUNCTION_GREATEREQUAL", "ACT_DOD_RELOAD_GARAND", "DMenu", "ACT_WALK_HURT", "ACT_DI_ALYX_ANTLION", "ACT_VM_PRIMARYATTACK_DEPLOYED_3", "SCHED_TAKE_COVER_FROM_ENEMY", "ACT_MP_ATTACK_SWIM_PREFIRE", "ACT_DOD_RUN_AIM_GREN_STICK", "ACT_VM_SPRINT_IDLE", "ACT_DOD_CROUCHWALK_AIM_GREN_STICK", "ACT_MP_RELOAD_AIRWALK_SECONDARY_LOOP", "saverestore", "MASK_OPAQUE_AND_NPCS", "ACT_VM_DEPLOY_5", "ai", "surface", "ACT_GESTURE_FLINCH_RIGHTARM", "ACT_SCRIPT_CUSTOM_MOVE", "ACT_DOD_PRONE_ZOOM_PSCHRECK", "ACT_VM_PULLPIN", "ACT_DOD_RELOAD_PRONE_K43", "HULL_HUMAN", "ACT_IDLE_ANGRY_SMG1", "CAP_NO_HIT_PLAYER", "ACT_DOD_PRONE_AIM_BAZOOKA", "ACT_HL2MP_RUN_SCARED", "ACT_GET_DOWN_CROUCH", "ACT_RUN_PISTOL", "ACT_MP_GESTURE_VC_HANDMOUTH_PRIMARY", "DProperties", "ACT_DOD_WALK_AIM_MG", "ACT_SLAM_THROW_THROW_ND", "ACT_STRAFE_RIGHT", "COLLISION_GROUP_WORLD", "ACT_DOD_PRIMARYATTACK_PISTOL", "NAV_MESH_HAS_ELEVATOR", "DGrid", "ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE", "ACT_DOD_CROUCH_ZOOMED", "DMG_FALL", "DImage", "COND_FLOATING_OFF_GROUND", "OBS_MODE_FREEZECAM", "ChangeBackground", "cookie", "GetOverlayPanel", "MAT_GRASS", "ACT_HL2MP_GESTURE_RELOAD_GRENADE", "ACT_MP_JUMP_START_PRIMARY", "constraint", "DListView_Line", "ACT_IDLE_PISTOL", "ACT_DOD_PRIMARYATTACK_PSCHRECK", "MOUSE_LEFT", "kRenderFxPulseSlowWide", "ACT_HL2MP_WALK_CROUCH_REVOLVER", "ACT_ZOMBIE_LEAP_START", "ACT_VM_IDLE_TO_LOWERED", "KEY_B", "MOVETYPE_WALK", "ACT_RIDE_MANNED_GUN", "ACT_MP_JUMP_PRIMARY", "ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN", "ACT_DOD_STAND_AIM_SPADE", "SortedPairs", "ACT_CROUCHIDLE_AGITATED", "ACT_SMG2_RELOAD2", "KEY_XBUTTON_LEFT", "NAV_MESH_NO_MERGE", "NORTH_EAST", "ACT_CROUCHING_GRENADEIDLE", "ACT_VM_PRIMARYATTACK_3", "ACT_HANDGRENADE_THROW2", "ACT_DOD_RELOAD_PRONE_M1CARBINE", "ACT_GESTURE_RANGE_ATTACK_AR2", "ACT_MP_ATTACK_STAND_GRENADE_BUILDING", "COND_SEE_HATE", "ACT_MP_ATTACK_SWIM_BUILDING", "ACT_DOD_STAND_AIM_30CAL", "BLEND_ONE_MINUS_SRC_COLOR", "ACT_IDLE_AIM_RIFLE_STIMULATED", "COND_CAN_RANGE_ATTACK2", "ACT_DOD_RELOAD_CROUCH_BOLT", "DeriveGamemode", "ACT_HL2MP_JUMP_MELEE2", "ACT_MP_GESTURE_VC_FISTPUMP_PDA", "FVPHYSICS_DMG_SLICE", "ACT_SIGNAL3", "ACT_SHIELD_UP", "ACT_GESTURE_RANGE_ATTACK_AR2_GRENADE", "ACT_HL2MP_SWIM_IDLE_PHYSGUN", "ACT_DOD_STAND_IDLE_GREASE", "ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE", "TYPE_LIGHTUSERDATA", "ACT_DOD_CROUCH_IDLE_MP44", "ACT_VM_DFIREMODE", "setmetatable", "SNDLVL_35dB", "ACT_ZOMBIE_LEAPING", "ACT_DOD_RELOAD_PRONE", "ACT_SHIELD_UP_IDLE", "ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED", "KEY_I", "ACT_HL2MP_SIT_RPG", "ACT_DOD_RELOAD_TOMMY", "getmetatable", "KEY_N", "ACT_SLAM_THROW_ND_DRAW", "ACT_HL2MP_WALK_CROUCH_ZOMBIE", "ACT_HL2MP_IDLE_ZOMBIE", "rawset", "CNewParticleEffect", "ACT_MP_GESTURE_VC_FINGERPOINT_MELEE", "ACT_HL2MP_RUN_FAST", "EFL_NO_GAME_PHYSICS_SIMULATION", "ACT_SHIELD_DOWN", "JOYSTICK_LAST_POV_BUTTON", "ACT_DOD_RUN_AIM_MP44", "SNDLVL_85dB", "SURF_NOLIGHT", "GetDefaultLoadingHTML", "ACT_VM_SPRINT_LEAVE", "ACT_DOD_PRIMARYATTACK_PRONE_BAR", "SCHED_DISARM_WEAPON", "KEY_XBUTTON_STICK2", "os", "ACT_DOD_PRONE_AIM_GREN_STICK", "ACT_VM_RELEASE", "ACT_HL2MP_WALK_SLAM", "Derma_Anim", "ACT_HL2MP_SWIM_IDLE_GRENADE", "ACT_VM_IDLE_3", "ENT_ANIM", "ACT_MP_RELOAD_CROUCH", "construct", "ACT_DOD_RUN_IDLE_MG", "KEY_V", "ACT_DOD_SPRINT_IDLE_TNT", "EFL_KILLME", "ACT_VM_LOWERED_TO_IDLE", "COND_HEAR_DANGER", "ACT_MP_GESTURE_VC_THUMBSUP_PDA", "ACT_MP_STAND_MELEE", "COND_NPC_UNFREEZE", "CLuaParticle", "ACT_MP_GESTURE_VC_FISTPUMP_PRIMARY", "ACT_HL2MP_IDLE_CROUCH_SUITCASE", "ACT_SMALL_FLINCH", "TEXTUREFLAGS_ALL_MIPS", "EyeVector", "ACT_ROLL_RIGHT", "ACT_PHYSCANNON_UPGRADE", "ACT_MP_MELEE_GRENADE1_ATTACK", "ACT_DI_ALYX_ZOMBIE_MELEE", "SendUserMessage", "ACT_SHOTGUN_PUMP", "FL_SWIM", "Tool", "PrecacheSentenceFile", "ACT_DOD_CROUCHWALK_IDLE_TOMMY", "ACT_DIE_GUTSHOT", "ACT_RPG_HOLSTER_UNLOADED", "ACT_RUN_AIM_SHOTGUN", "ACT_RANGE_ATTACK2_LOW", "DListBoxItem", "ACT_DOD_STAND_AIM", "ACT_DOD_RUN_ZOOM_BAZOOKA", "CNavLadder", "ACT_READINESS_AGITATED_TO_STIMULATED", "ACT_READINESS_STIMULATED_TO_RELAXED", "ACT_DOD_HS_CROUCH", "ACT_BUSY_SIT_GROUND_EXIT", "ACT_HL2MP_WALK_CROUCH_MELEE2", "ACT_VM_UNDEPLOY_5", "EFL_NO_DAMAGE_FORCES", "ACT_MP_RELOAD_STAND_PRIMARY", "KEY_PAD_3", "ACT_DOD_WALK_IDLE_TOMMY", "COLLISION_GROUP_NPC", "ispanel", "ACT_RELOAD_LOW", "KEY_PAD_DECIMAL", "ACT_DOD_RELOAD_CROUCH_BAZOOKA", "DTree_Node", "ACT_RPG_FIDGET_UNLOADED", "AchievementIcon", "ACT_BARNACLE_HIT", "ACT_DOD_RUN_AIM_MP40", "ACT_HL2MP_GESTURE_RELOAD_CAMERA", "gmod", "IconEditor", "MatSelect", "ACT_VM_IDLE_DEPLOYED_2", "SNDLVL_105dB", "ACT_DOD_CROUCH_AIM_MP40", "ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN", "DLabel", "ACT_HL2MP_IDLE_CROUCH_SMG1", "ACT_RUN", "ACT_VM_PULLBACK", "FCVAR_PRINTABLEONLY", "ACT_SIGNAL1", "KEY_DELETE", "EmitSound", "CHAN_VOICE", "DListView", "STUDIO_GENERATE_STATS", "ACT_SWIM", "ACT_DOD_RELOAD_PISTOL", "COND_ENEMY_FACING_ME", "TYPE_FUNCTION", "ACT_DOD_PRIMARYATTACK_DEPLOYED_30CAL", "SCHED_PATROL_RUN", "ACT_MP_ATTACK_STAND_SECONDARY", "ACT_VM_UNDEPLOY_6", "ACT_VM_UNDEPLOY_3", "COND_HAVE_TARGET_LOS", "EFL_TOUCHING_FLUID", "ACT_DOD_RUN_AIM_C96", "ACT_GESTURE_RANGE_ATTACK_TRIPWIRE", "ACT_HL2MP_SWIM_IDLE_REVOLVER", "ACT_HL2MP_IDLE_CROSSBOW", "ACT_DOD_PRONE_DEPLOY_30CAL", "ACT_DOD_PRONEWALK_IDLE_PISTOL", "ACT_OVERLAY_SHIELD_KNOCKBACK", "util", "ACT_DOD_CROUCH_IDLE_BAR", "ACT_HL2MP_SWIM_SUITCASE", "ACT_PHYSCANNON_ANIMATE_PRE", "IncludeCS", "package", "ACT_COVER_LOW", "NumDownloadables", "BOX_RIGHT", "ACT_MP_ATTACK_SWIM_GRENADE_PRIMARY", "ACT_MP_RELOAD_CROUCH_PRIMARY_END", "ACT_OBJ_STARTUP", "FVPHYSICS_HEAVY_OBJECT", "ACT_HL2MP_JUMP_PHYSGUN", "CurTime", "WEAPON_PROFICIENCY_VERY_GOOD", "COND_HEALTH_ITEM_AVAILABLE", "ACT_IDLE_AGITATED", "OBS_MODE_NONE", "GetRenderTargetEx", "RENDERGROUP_OPAQUE_HUGE", "WEAPON_PROFICIENCY_AVERAGE", "ACT_HL2MP_WALK_ANGRY", "COLLISION_GROUP_DEBRIS_TRIGGER", "USE_TOGGLE", "TEXT_ALIGN_RIGHT", "USE_SET", "ACT_MP_GRENADE1_IDLE", "TYPE_MATERIAL", "DOF_Start", "OBS_MODE_CHASE", "CLASS_VORTIGAUNT", "TYPE_VIDEO", "TYPE_VECTOR", "TYPE_USERMSG", "FCVAR_ARCHIVE", "ACT_VM_RECOIL3", "KEY_H", "TYPE_USERCMD", "TYPE_THREAD", "ChangeTooltip", "ACT_DOD_SPRINT_IDLE_MP44", "ACT_HL2MP_WALK_MELEE", "SlideBar", "ACT_VM_MISSCENTER", "NextBot", "ACT_MP_DOUBLEJUMP", "DTextEntry", "JS_Utility", "TYPE_SOUNDHANDLE", "DScrollPanel", "ACT_VM_DEPLOY_3", "ACT_MP_JUMP_START_SECONDARY", "TEXTUREFLAGS_UNUSED_80000000", "ACT_GESTURE_RELOAD", "ACT_MP_GESTURE_VC_NODNO_PDA", "MOVECOLLIDE_FLY_CUSTOM", "ACT_HL2MP_IDLE_AR2", "TYPE_SOUND", "TYPE_SCRIPTEDVEHICLE", "TYPE_RESTORE", "ACT_MP_GESTURE_VC_HANDMOUTH", "TYPE_RECIPIENTFILTER", "TYPE_PROJECTEDTEXTURE", "ACT_MP_JUMP_LAND", "ACT_MELEE_ATTACK2", "ACT_VM_DRYFIRE_LEFT", "IRestore", "ACT_DOD_CROUCHWALK_ZOOMED", "ACT_VM_IDLE_7", "CONTENTS_DETAIL", "TYPE_PIXELVISHANDLE", "TYPE_PHYSOBJ", "SCHED_COMBAT_STAND", "IMesh", "ACT_DOD_CROUCH_AIM_MG", "MAT_TILE", "ACT_STEP_FORE", "JOYSTICK_LAST_BUTTON", "TYPE_PATH", "ACT_DOD_CROUCH_AIM_GREN_STICK", "TYPE_PARTICLEEMITTER", "ACT_FIRE_LOOP", "ACT_GET_UP_STAND", "TYPE_PARTICLE", "TYPE_PANEL", "TYPE_NUMBER", "TYPE_NIL", "EFL_SERVER_ONLY", "gameevent", "ACT_SIGNAL_RIGHT", "ACT_SLAM_THROW_TO_STICKWALL_ND", "ACT_DOD_RELOAD_CROUCH_C96", "ACT_DOD_RELOAD_BAZOOKA", "TYPE_NAVAREA", "ACT_FLINCH_LEFTLEG", "TYPE_MOVEDATA", "ACT_RUN_CROUCH", "ACT_SWIM_IDLE", "TYPE_MATRIX", "USE_ON", "ACT_MP_PRIMARY_GRENADE1_DRAW", "ACT_VM_IDLE_SILENCED", "ACT_RPG_DRAW_UNLOADED", "ACT_VM_DEPLOY_2", "ACT_CLIMB_DOWN", "TYPE_LOCOMOTION", "ModelImage", "FSOLID_FORCE_WORLD_ALIGNED", "IN_WALK", "ACT_DOD_CROUCHWALK_AIM_GREN_FRAG", "ACT_BUSY_SIT_GROUND", "TYPE_IMESH", "ACT_GESTURE_FLINCH_STOMACH", "ACT_CROUCHIDLE_AIM_STIMULATED", "ACT_DOD_RELOAD_PRONE_PSCHRECK", "TYPE_ENTITY", "Model", "TEXTUREFLAGS_POINTSAMPLE", "IMAGE_FORMAT_BGRA8888", "TYPE_DAMAGEINFO", "ACT_HL2MP_SWIM_IDLE_KNIFE", "ACT_DOD_CROUCH_AIM_SPADE", "ACT_DOD_STAND_AIM_GREN_FRAG", "ClientsideModel", "TYPE_COUNT", "RemoveTooltip", "COND_BEHIND_ENEMY", "TYPE_ANGLE", "NAV_MESH_RUN", "KEY_F9", "CEffectData", "TRANSMIT_NEVER", "TRANSMIT_ALWAYS", "ACT_MP_ATTACK_SWIM_GRENADE_BUILDING", "SCHED_AMBUSH", "MATERIAL_TRIANGLE_STRIP", "ai_task", "STUDIO_WIREFRAME_VCOLLIDE", "EF_NORECEIVESHADOW", "ACT_HL2MP_RUN_MELEE", "TRACER_LINE", "net", "AngleRand", "COND_TOO_FAR_TO_ATTACK", "ACT_GMOD_TAUNT_ROBOT", "ACT_SLAM_DETONATOR_THROW_DRAW", "ACT_RANGE_ATTACK_SHOTGUN", "ACT_SLAM_DETONATOR_DRAW", "ACT_MP_ATTACK_SWIM_GRENADE_SECONDARY", "TEXTUREFLAGS_UNUSED_10000000", "TEXTUREFLAGS_SSBUMP", "TEXTUREFLAGS_VERTEXTEXTURE", "ACT_READINESS_RELAXED_TO_STIMULATED_WALK", "TEXTUREFLAGS_CLAMPU", "TEXTUREFLAGS_UNUSED_01000000", "TEXTUREFLAGS_NODEPTHBUFFER", "CONTENTS_CURRENT_0", "ACT_HL2MP_WALK_CROUCH_RPG", "DSlider", "util.worldpicker", "ACT_HL2MP_SWIM_IDLE_SMG1", "ACT_HL2MP_WALK_GRENADE", "ContextBase", "ACT_MP_RELOAD_STAND_SECONDARY", "ACT_IDLETORUN", "NORTH", "IN_ATTACK", "ACT_OVERLAY_GRENADEIDLE", "COND_BETTER_WEAPON_AVAILABLE", "TEXTUREFLAGS_IMMEDIATE_CLEANUP", "TEXTUREFLAGS_NODEBUGOVERRIDE", "KEY_F11", "TEXTUREFLAGS_RENDERTARGET", "ACT_IDLE_ANGRY_MELEE", "TEXTUREFLAGS_EIGHTBITALPHA", "TEXTUREFLAGS_ONEBITALPHA", "SuppressHostEvents", "IN_USE", "ACT_DOD_RELOAD_BAR", "NAV_MESH_NO_JUMP", "ACT_HL2MP_IDLE_CROUCH_MELEE2", "BLEND_DST_COLOR", "CONTENTS_MOVEABLE", "TEXTUREFLAGS_NOMIP", "ACT_VM_RELOAD_INSERT_PULL", "render", "ACT_MP_ATTACK_CROUCH_MELEE", "ACT_HL2MP_SWIM_IDLE_FIST", "ACT_DOD_PRONEWALK_IDLE_30CAL", "TEXTUREFLAGS_PWL_CORRECTED", "TEXTUREFLAGS_HINT_DXT5", "ACT_DOD_CROUCHWALK_AIM_PISTOL", "TEXTUREFLAGS_ANISOTROPIC", "TEXTUREFLAGS_CLAMPT", "TEXTUREFLAGS_CLAMPS", "navmesh", "TEXT_ALIGN_TOP", "TEXTUREFLAGS_TRILINEAR", "TEXT_ALIGN_LEFT", "ACT_GESTURE_RELOAD_PISTOL", "SCHED_RUN_FROM_ENEMY_MOB", "ACT_90_LEFT", "TEAM_SPECTATOR", "ENT_AI", "TEAM_UNASSIGNED", "TEAM_CONNECTING", "IsUselessModel", "BLEND_SRC_ALPHA", "COND_ENEMY_DEAD", "ACT_MP_RELOAD_CROUCH_SECONDARY_LOOP", "MATERIAL_CULLMODE_CCW", "COND_NO_WEAPON", "SURF_NOSHADOWS", "COND_HAVE_ENEMY_LOS", "SURF_BUMPLIGHT", "FCVAR_NONE", "SURF_SKIP", "SURF_HINT", "HULL_TINY", "ACT_SLAM_TRIPMINE_ATTACH", "ControlPanel", "ACT_STAND", "SURF_TRIGGER", "LocalToWorld", "drive", "ACT_DOD_STAND_AIM_BAR", "GetMapList", "ACT_MP_ATTACK_STAND_STARTFIRE", "SURF_NOPORTAL", "ACT_MP_SWIM", "ACT_MP_GESTURE_VC_FINGERPOINT_PDA", "SURF_TRANS", "ACT_GESTURE_RANGE_ATTACK_HMG1", "GESTURE_SLOT_ATTACK_AND_RELOAD", "SURF_SKY", "ACT_ARM", "SURF_LIGHT", "DMG_VEHICLE", "ACT_DOD_SPRINT_IDLE_C96", "FL_WATERJUMP", "STUDIO_SSAODEPTHTEXTURE", "ACT_MP_SPRINT", "STUDIO_NOSHADOWS", "ACT_RELOAD_FINISH", "STUDIO_ITEM_BLINK", "ACT_DOD_RUN_IDLE_TNT", "ACT_VM_RECOIL2", "CLASS_PLAYER", "SCHED_WAKE_ANGRY", "STUDIO_VIEWXFORMATTACHMENTS", "ACT_CROSSBOW_DRAW_UNLOADED", "ACT_MP_GESTURE_VC_FISTPUMP_BUILDING", "STUDIO_RENDER", "ACT_MP_CROUCHWALK", "bit", "ACT_LOOKBACK_RIGHT", "STEPSOUNDTIME_WATER_FOOT", "CAP_INNATE_MELEE_ATTACK1", "KEY_F", "STEPSOUNDTIME_WATER_KNEE", "KEY_1", "ACT_MP_GESTURE_VC_NODYES_PDA", "STEPSOUNDTIME_ON_LADDER", "STEPSOUNDTIME_NORMAL", "STENCILOPERATION_DECR", "STENCILOPERATION_INCR", "STENCILOPERATION_INVERT", "STENCILOPERATION_DECRSAT", "ACT_SLAM_DETONATOR_DETONATE", "ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE", "STENCILOPERATION_INCRSAT", "MOVETYPE_OBSERVER", "ACT_WALK_AIM_RIFLE", "ACT_DOD_SECONDARYATTACK_PRONE_RIFLE", "STENCILOPERATION_ZERO", "ACT_MP_ATTACK_SWIM_PRIMARY", "STENCILOPERATION_KEEP", "ACT_DOD_STAND_IDLE_PSCHRECK", "jit", "FSOLID_NOT_STANDABLE", "ACT_MP_RELOAD_SWIM_PRIMARY_LOOP", "CLASS_PLAYER_ALLY", "ACT_VM_IDLE_EMPTY_LEFT", "STENCILCOMPARISONFUNCTION_NOTEQUAL", "STENCILCOMPARISONFUNCTION_GREATER", "STENCILCOMPARISONFUNCTION_LESSEQUAL", "ACT_DOD_CROUCH_AIM_BAZOOKA", "Derma_Query", "STENCILCOMPARISONFUNCTION_EQUAL", "ACT_DOD_CROUCHWALK_IDLE_BOLT", "EF_DIMLIGHT", "STENCILCOMPARISONFUNCTION_NEVER", "STENCIL_INCR", "STENCIL_INVERT", "ACT_HL2MP_SWIM_IDLE_ANGRY", "NODOCK", "STENCIL_DECRSAT", "ControlPresets", "STENCIL_INCRSAT", "STENCIL_REPLACE", "KEY_XSTICK2_DOWN", "PLAYERANIMEVENT_ATTACK_SECONDARY", "STENCIL_ZERO", "ACT_GESTURE_RANGE_ATTACK1", "ACT_GESTURE_FLINCH_CHEST", "STENCIL_ALWAYS", "STENCIL_GREATEREQUAL", "STENCIL_NOTEQUAL", "GetConVar_Internal", "DColorButton", "ACT_IDLE_CARRY", "FCVAR_CLIENTCMD_CAN_EXECUTE", "MAT_GRATE", "ACT_MP_WALK_PRIMARY", "STENCIL_LESSEQUAL", "STENCIL_EQUAL", "ACT_MP_JUMP_BUILDING", "HULL_WIDE_HUMAN", "STENCIL_NEVER", "ISave", "CLASS_COMBINE", "SOLID_CUSTOM", "ACT_DOD_RUN_IDLE", "ACT_GESTURE_RELOAD_SHOTGUN", "ACT_SLAM_STICKWALL_ND_DRAW", "ACT_SHOTGUN_RELOAD_START", "ACT_HL2MP_IDLE_CROUCH_GRENADE", "Mesh", "ACT_MP_RELOAD_SWIM_SECONDARY", "ACT_MP_JUMP_LAND_PDA", "ACT_DOD_SECONDARYATTACK_MP40", "SOLID_BSP", "ACT_DOD_SPRINT_IDLE_BAZOOKA", "ACT_DOD_PRONEWALK_IDLE_RIFLE", "SF_NPC_LONG_RANGE", "ACT_VM_UNDEPLOY_7", "ACT_VM_RELOAD_INSERT", "PLAYER_JUMP", "SNDLVL_150dB", "NAV_MESH_STAIRS", "SNDLVL_GUNFIRE", "SCHED_WAIT_FOR_SCRIPT", "ACT_BUSY_QUEUE", "ACT_RANGE_ATTACK1_LOW", "HSVToColor", "EF_ITEM_BLINK", "ACT_VM_PRIMARYATTACK_DEPLOYED_7", "SNDLVL_120dB", "PrintTable", "ACT_HL2MP_GESTURE_RELOAD_KNIFE", "ACT_HL2MP_SWIM_IDLE_AR2", "DNumberScratch", "SNDLVL_110dB", "ACT_MP_STAND_SECONDARY", "SNDLVL_100dB", "DMG_SONIC", "ACT_SLAM_THROW_DRAW", "ACT_MP_SWIM_MELEE", "ACT_DOD_ZOOMLOAD_PSCHRECK", "mesh", "SCHED_RANGE_ATTACK2", "ACT_MP_ATTACK_AIRWALK_GRENADE_SECONDARY", "SNDLVL_TALKING", "KEY_D", "ACT_OBJ_IDLE", "SNDLVL_80dB", "EFL_DORMANT", "ACT_DOD_SECONDARYATTACK_PRONE_BOLT", "CLASS_BULLSEYE", "DMG_RADIATION", "SNDLVL_65dB", "ACT_MP_RELOAD_CROUCH_SECONDARY", "Entity", "ACT_MP_PRIMARY_GRENADE2_DRAW", "TEXTUREFLAGS_NOLOD", "SNDLVL_55dB", "ACT_BUSY_SIT_CHAIR_EXIT", "KEY_RBRACKET", "SNDLVL_40dB", "MASK_BLOCKLOS_AND_NPCS", "SNDLVL_30dB", "ACT_HL2MP_WALK_CROUCH_SCARED", "ACT_DOD_RUN_IDLE_BAR", "ACT_DOD_PRONE_AIM_BOLT", "DShape", "FCVAR_USERINFO", "ACT_MP_RELOAD_AIRWALK_END", "CONTENTS_AREAPORTAL", "SNDLVL_25dB", "team", "ACT_DOD_HS_CROUCH_K98", "ACT_GMOD_TAUNT_PERSISTENCE", "DExpandButton", "COND_LOW_PRIMARY_AMMO", "RealTime", "ACT_VM_IDLE_M203", "ACT_RELOAD_SHOTGUN", "SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL", "ACT_DOD_WALK_AIM_TOMMY", "ACT_HL2MP_WALK_CAMERA", "MOVETYPE_FLY", "SND_SHOULDPAUSE", "ACT_SHOTGUN_RELOAD_FINISH", "ACT_MP_ATTACK_STAND_PRIMARYFIRE_DEPLOYED", "ACT_SHIPLADDER_DOWN", "ACT_MP_GESTURE_FLINCH_PRIMARY", "SND_DELAY", "SOUTH", "ACT_DOD_CROUCH_AIM_GREASE", "SND_STOP", "ACT_CROSSBOW_HOLSTER_UNLOADED", "ACT_VM_IDLE_DEPLOYED_7", "ACT_DOD_SPRINT_AIM_GREN_FRAG", "ACT_DOD_HS_IDLE_TOMMY", "SND_CHANGE_PITCH", "ACT_GESTURE_RANGE_ATTACK_PISTOL", "SND_CHANGE_VOL", "SND_NOFLAGS", "SIM_GLOBAL_FORCE", "CAP_INNATE_RANGE_ATTACK2", "ACT_DOD_PRIMARYATTACK_PRONE_30CAL", "ACT_HL2MP_WALK_PHYSGUN", "ACT_HL2MP_WALK_CROUCH_PHYSGUN", "ACT_DOD_STAND_ZOOM_BOLT", "ACT_MP_GESTURE_VC_NODNO_PRIMARY", "ACT_DOD_RUN_AIM_BOLT", "SIM_LOCAL_ACCELERATION", "SIM_NOTHING", "ACT_DOD_SECONDARYATTACK_CROUCH_MP40", "ACT_DOD_PRONE_ZOOM_RIFLE", "ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL", "KEY_XBUTTON_LEFT_SHOULDER", "effects", "UnPredictedCurTime", "ACT_HL2MP_IDLE_CROUCH_ZOMBIE", "SCHED_FLINCH_PHYSICS", "SF_PHYSBOX_MOTIONDISABLED", "ACT_DOD_WALK_AIM_MP44", "PLAYERANIMEVENT_FLINCH_RIGHTARM", "EyeAngles", "SF_NPC_WAIT_TILL_SEEN", "ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN", "SF_NPC_WAIT_FOR_SCRIPT", "SF_NPC_TEMPLATE", "SF_NPC_START_EFFICIENT", "BLOOD_COLOR_MECH", "SOLID_NONE", "gcinfo", "SF_NPC_FALL_TO_GROUND", "concommand", "SF_NPC_DROP_HEALTHKIT", "BLOOD_COLOR_ZOMBIE", "PLAYER_IN_VEHICLE", "SF_FLOOR_TURRET_CITIZEN", "Derma_Hook", "SF_CITIZEN_USE_RENDER_BOUNDS", "SF_CITIZEN_RANDOM_HEAD_MALE", "DNumPad", "ACT_RUN_AIM_RIFLE_STIMULATED", "ACT_DOD_PRONE_AIM_PSCHRECK", "FL_DONTTOUCH", "HULL_MEDIUM_TALL", "DMG_PARALYZE", "ACT_MP_RELOAD_STAND_END", "system", "SF_CITIZEN_IGNORE_SEMAPHORE", "SF_CITIZEN_FOLLOW", "KEY_SPACE", "SF_CITIZEN_AMMORESUPPLIER", "FL_INRAIN", "KEY_XBUTTON_UP", "URLLabel", "ACT_WALK_PISTOL", "KEY_PAGEDOWN", "STUDIO_DRAWTRANSLUCENTSUBMODELS", "SCHED_WAIT_FOR_SPEAK_FINISH", "ACT_IDLE_RPG", "SNDLVL_140dB", "SCHED_TARGET_FACE", "MAT_ANTLION", "IMAGE_FORMAT_RGBA8888", "ACT_VM_IDLE_2", "ACT_VM_THROW", "SCHED_TARGET_CHASE", "SCHED_TAKE_COVER_FROM_BEST_SOUND", "ACT_DOD_SPRINT_IDLE_PISTOL", "ACT_SHIPLADDER_UP", "SCHED_SWITCH_TO_PENDING_WEAPON", "SCHED_STANDOFF", "SCHED_SPECIAL_ATTACK2", "SCHED_SPECIAL_ATTACK1", "SCHED_SMALL_FLINCH", "SCHED_SLEEP", "SCHED_SHOOT_ENEMY_COVER", "ACT_DOD_PRIMARYATTACK_BAZOOKA", "SCHED_SCRIPTED_WALK", "SCHED_SCRIPTED_WAIT", "SCHED_SCRIPTED_RUN", "SCHED_SCRIPTED_CUSTOM_MOVE", "COND_NO_HEAR_DANGER", "SCHED_RUN_RANDOM", "SCHED_RUN_FROM_ENEMY_FALLBACK", "ACT_DOD_PRONE_AIM_C96", "KEY_LCONTROL", "MATERIAL_FOG_LINEAR_BELOW_FOG_Z", "ACT_DOD_PRIMARYATTACK_CROUCH_KNIFE", "SCHED_RANGE_ATTACK1", "GESTURE_SLOT_CUSTOM", "MAT_FOLIAGE", "ACT_DOD_RELOAD_PRONE_RIFLE", "DListView_DraggerBar", "SURF_NODECALS", "SCHED_NONE", "kRenderFxSpotlight", "ACT_DOD_RELOAD_MP40", "RENDERMODE_TRANSTEXTURE", "ACT_VM_UNUSABLE", "ACT_WALK_RPG_RELAXED", "SCHED_NEW_WEAPON", "ACT_GESTURE_TURN_LEFT45", "ACT_SMG2_IDLE2", "SCHED_MOVE_AWAY_FROM_ENEMY", "SCHED_MOVE_AWAY_FAIL", "ACT_DEPLOY", "next", "ACT_OBJ_DETERIORATING", "SCHED_MOVE_AWAY_END", "IN_WEAPON1", "ACT_DOD_CROUCHWALK_AIM_BAZOOKA", "ACT_SMG2_DRAW2", "VectorRand", "cvars", "SCHED_MOVE_AWAY", "DMenuOptionCVar", "SCHED_MELEE_ATTACK2", "KEY_T", "COND_WEAPON_PLAYER_IN_SPREAD", "SCHED_INVESTIGATE_SOUND", "ACT_MP_RELOAD_STAND", "utf8", "CONTENTS_GRATE", "MATERIAL_QUADS", "SCHED_INTERACTION_MOVE_TO_PARTNER", "IMaterial", "ACT_DOD_CROUCHWALK_AIM_PSCHRECK", "ACT_SLAM_STICKWALL_TO_THROW_ND", "SCHED_IDLE_WANDER", "SCHED_IDLE_WALK", "SCHED_IDLE_STAND", "ACT_MP_JUMP_FLOAT_PRIMARY", "ACT_DOD_CROUCH_AIM_C96", "ACT_GESTURE_RANGE_ATTACK_PISTOL_LOW", "IsFriendEntityName", "EFFECT", "DProperty_Combo", "KEY_CAPSLOCKTOGGLE", "SCHED_FORCED_GO_RUN", "select", "SCHED_FORCED_GO", "ACT_DOD_SECONDARYATTACK_TOMMY", "RestoreCursorPosition", "SF_PHYSPROP_MOTIONDISABLED", "SCHED_FEAR_FACE", "ACT_SLAM_DETONATOR_HOLSTER", "ACT_DOD_RELOAD_RIFLE", "FVPHYSICS_CONSTRAINT_STATIC", "SCHED_FALL_TO_GROUND", "ACT_MP_ATTACK_CROUCH_MELEE_SECONDARY", "DMG_NERVEGAS", "SCHED_FAIL_NOSTOP", "SCHED_FAIL_ESTABLISH_LINE_OF_FIRE", "SCHED_FAIL", "SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK", "ACT_HL2MP_WALK_CROUCH_SHOTGUN", "ACT_VM_IOUT_M203", "ACT_RUN_RIFLE", "ACT_HL2MP_RUN_PROTECTED", "ACT_PLAYER_CROUCH_WALK_FIRE", "SCHED_DROPSHIP_DUSTOFF", "SCHED_DIE_RAGDOLL", "IN_RIGHT", "SCHED_DIE", "ACT_MP_ATTACK_CROUCH_SECONDARY", "SCHED_COWER", "MASK_PLAYERSOLID", "ACT_SLAM_THROW_TO_STICKWALL", "SCHED_COMBAT_FACE", "COLLISION_GROUP_PUSHAWAY", "DEntityProperties", "SCHED_CHASE_ENEMY", "ToggleFavourite", "SCHED_BACK_AWAY_FROM_ENEMY", "HUD_PRINTCENTER", "ACT_MP_STAND_PDA", "ACT_DOD_RELOAD_DEPLOYED_FG42", "SCHED_ARM_WEAPON", "DMenuBar", "ACT_HL2MP_IDLE_SHOTGUN", "SF_CITIZEN_RANDOM_HEAD_FEMALE", "ACT_MP_GESTURE_VC_THUMBSUP_BUILDING", "FSOLID_ROOT_PARENT_ALIGNED", "SCHED_ALERT_SCAN", "SCHED_ALERT_REACT_TO_COMBAT_SOUND", "SCHED_ALERT_FACE_BESTSOUND", "SCHED_AISCRIPT", "ACT_HL2MP_WALK_SUITCASE", "ACT_VM_UNDEPLOY_1", "ACT_VM_ISHOOTDRY", "ACT_DOD_PRONE_ZOOM_FORWARD_BOLT", "ACT_RANGE_ATTACK1", "ACT_VM_UNUSABLE_TO_USABLE", "LAST_SHARED_SCHEDULE", "IMAGE_FORMAT_RGB888", "RT_SIZE_OFFSCREEN", "FrameTime", "RT_SIZE_FULL_FRAME_BUFFER", "COND_HEAR_COMBAT", "RT_SIZE_HDR", "ACT_DOD_CROUCHWALK_AIM_C96", "RT_SIZE_PICMIP", "ACT_MP_GESTURE_FLINCH_RIGHTLEG", "RT_SIZE_DEFAULT", "PLAYER_RELOAD", "DIconLayout", "RT_SIZE_NO_CHANGE", "COND_HEAR_PLAYER", "ACT_VM_CRAWL_M203", "RENDERMODE_WORLDGLOW", "RENDERMODE_TRANSADDFRAMEBLEND", "MATERIAL_RT_DEPTH_SHARED", "COND_HEAR_THUMPER", "CT_DEFAULT", "RENDERMODE_TRANSADD", "ACT_RANGE_AIM_AR2_LOW", "ACT_VM_PRIMARYATTACK_DEPLOYED_4", "RENDERMODE_TRANSALPHA", "RENDERMODE_GLOW", "ACT_HL2MP_IDLE_CROUCH_PASSIVE", "SCHED_NEW_WEAPON_CHEAT", "CAP_MOVE_SWIM", "DListViewHeaderLabel", "KEY_UP", "ACT_CLIMB_UP", "RENDERMODE_NORMAL", "ACT_HL2MP_RUN_ANGRY", "RENDERGROUP_OTHER", "COND_NEW_ENEMY", "RENDERGROUP_OPAQUE_BRUSH", "ACT_DOD_SPRINT_AIM_SPADE", "ACT_DOD_CROUCH_ZOOM_BAZOOKA", "KEY_BACKQUOTE", "RENDERGROUP_VIEWMODEL_TRANSLUCENT", "ACT_WALK_SCARED", "ACT_VM_MISSCENTER2", "ACT_MELEE_ATTACK1", "RENDERGROUP_STATIC_HUGE", "KEY_L", "PLAYERANIMEVENT_SPAWN", "PLAYERANIMEVENT_SNAP_YAW", "ACT_DOD_RELOAD_CROUCH_RIFLE", "PLAYERANIMEVENT_RELOAD_LOOP", "PLAYERANIMEVENT_RELOAD_END", "ACT_IDLE_HURT", "PLAYERANIMEVENT_RELOAD", "ACT_HL2MP_GESTURE_RELOAD_SMG1", "KEY_U", "ACT_DOD_CROUCH_IDLE_TOMMY", "PLAYERANIMEVENT_FLINCH_LEFTLEG", "PLAYERANIMEVENT_FLINCH_HEAD", "GESTURE_SLOT_SWIM", "PLAYERANIMEVENT_FLINCH_CHEST", "CHAN_VOICE2", "ACT_MP_ATTACK_STAND_MELEE", "PLAYERANIMEVENT_DOUBLEJUMP", "PLAYERANIMEVENT_DIE", "PLAYERANIMEVENT_CUSTOM_SEQUENCE", "ACT_VM_DRAWFULL_M203", "ACT_DOD_PRIMARYATTACK_GREN_FRAG", "IN_WEAPON2", "PLAYERANIMEVENT_CUSTOM_GESTURE_SEQUENCE", "ACT_HL2MP_SWIM_IDLE_SCARED", "PLAYERANIMEVENT_CUSTOM_GESTURE", "PLAYERANIMEVENT_CUSTOM", "ServerLog", "PLAYERANIMEVENT_CANCEL", "ACT_HL2MP_WALK_CROUCH_SMG1", "DOF_OFFSET", "PLAYERANIMEVENT_ATTACK_PRIMARY", "ParticleEffectAttach", "PLAYERANIMEVENT_ATTACK_GRENADE", "SafeRemoveEntity", "ACT_DOD_CROUCH_AIM_PISTOL", "RadioButton", "ACT_DOD_CROUCHWALK_IDLE_BAR", "CLuaEmitter", "ACT_MP_RELOAD_CROUCH_SECONDARY_END", "FL_STATICPROP", "ACT_RANGE_AIM_LOW", "FL_GRENADE", "EF_BONEMERGE_FASTCULL", "ACT_HL2MP_RUN_PHYSGUN", "ACT_DOD_PRIMARYATTACK_BAR", "PLAYER_ATTACK1", "ACT_MP_DEPLOYED_PRIMARY", "PLAYER_DIE", "PLAYER_SUPERJUMP", "setfenv", "COND_ENEMY_UNREACHABLE", "PATTACH_POINT_FOLLOW", "PATTACH_POINT", "CAP_FRIENDLY_DMG_IMMUNE", "ACT_MP_CROUCHWALK_PDA", "ACT_DOD_CROUCHWALK_AIM_GREASE", "ACT_VM_SECONDARYATTACK", "PATTACH_ABSORIGIN_FOLLOW", "ACT_DOD_PRIMARYATTACK_CROUCH_GREN_FRAG", "ACT_GESTURE_RELOAD_SMG1", "ACT_IDLE_ANGRY_RPG", "ACT_HL2MP_SWIM_PASSIVE", "CHAN_USER_BASE", "ACT_HL2MP_WALK_KNIFE", "ACT_RANGE_ATTACK_AR2_GRENADE", "ACT_DROP_WEAPON_SHOTGUN", "DMG_DROWNRECOVER", "OBS_MODE_IN_EYE", "ACT_DOD_PRIMARYATTACK_PRONE_MG", "EF_NODRAW", "ACT_DOD_WALK_IDLE_MP44", "MOUSE_FIRST", "OBS_MODE_FIXED", "ACT_HL2MP_WALK_ZOMBIE_01", "ACT_VM_PRIMARYATTACK_2", "WEAPON_PROFICIENCY_GOOD", "NUM_SPRITES", "scripted_ents", "KEY_BACKSPACE", "NUM_HULLS", "KEY_PAD_ENTER", "ACT_HL2MP_WALK_ZOMBIE_04", "serverlist", "NUM_AI_CLASSES", "ACT_TRIPMINE_WORLD", "DDragBase", "NPC_STATE_DEAD", "DCategoryHeader", "NPC_STATE_PRONE", "NPC_STATE_PLAYDEAD", "KEY_CAPSLOCK", "NPC_STATE_SCRIPT", "NPC_STATE_COMBAT", "ACT_HL2MP_WALK_RPG", "ACT_HL2MP_WALK_ZOMBIE_06", "NPC_STATE_ALERT", "ACT_IDLE_ANGRY_SHOTGUN", "NPC_STATE_NONE", "DMG_BLAST_SURFACE", "NOTIFY_HINT", "DPanelOverlay", "ACT_DYINGTODEAD", "NOTIFY_ERROR", "ACT_HL2MP_WALK_CROUCH_PISTOL", "NOTIFY_GENERIC", "ACT_HL2MP_IDLE_CROUCH_ZOMBIE_02", "GO_ELEVATOR_DOWN", "ACT_MP_CROUCH_BUILDING", "ACT_DOD_PRONEWALK_AIM_KNIFE", "ACT_HL2MP_JUMP_SCARED", "ACT_DOD_STAND_IDLE_MP40", "GO_ELEVATOR_UP", "PLAYERANIMEVENT_JUMP", "GO_LADDER_DOWN", "ACT_DOD_DEPLOY_RIFLE", "unpack", "ACT_VM_RELOAD_DEPLOYED", "GO_SOUTH", "ACT_COVER_LOW_RPG", "GO_EAST", "MATERIAL_LIGHT_POINT", "WEST", "ACT_GMOD_TAUNT_LAUGH", "EAST", "NUM_CORNERS", "SOUTH_WEST", "SOUTH_EAST", "MATERIAL_FOG_LINEAR", "BONE_SCREEN_ALIGN_SPHERE", "IN_ATTACK2", "NAV_MESH_AVOID", "NORTH_WEST", "ACT_DOD_RELOAD_PRONE_BOLT", "ACT_MP_WALK", "CAP_INNATE_RANGE_ATTACK1", "ACT_DOD_RELOAD_PRONE_DEPLOYED_30CAL", "NAV_MESH_NAV_BLOCKER", "ACT_DOD_PRIMARYATTACK_CROUCH", "NAV_MESH_CLIFF", "NAV_MESH_OBSTACLE_TOP", "NAV_MESH_NO_HOSTAGES", "NAV_MESH_STAND", "NAV_MESH_DONT_HIDE", "ACT_DOD_CROUCHWALK_IDLE", "DListView_Column", "NAV_MESH_TRANSIENT", "TRANSMIT_PVS", "TEXTUREFLAGS_PROCEDURAL", "NAV_MESH_PRECISE", "KEY_PAD_9", "ACT_SLAM_STICKWALL_ND_ATTACH2", "ACT_HOP", "NAV_MESH_CROUCH", "NAV_MESH_INVALID", "EF_BRIGHTLIGHT", "PLAYER_IDLE", "ACT_DOD_CROUCHWALK_AIM_MG", "ACT_HL2MP_IDLE_CROUCH_KNIFE", "rawget", "ACT_HL2MP_GESTURE_RELOAD_CROSSBOW", "ACT_SLAM_THROW_ND_IDLE", "MOVETYPE_LADDER", "CTakeDamageInfo", "input", "DForm", "ENTITY", "ACT_DOD_CROUCH_AIM_PSCHRECK", "Derma_StringRequest", "ACT_HL2MP_SWIM_CAMERA", "ACT_GMOD_GESTURE_TAUNT_ZOMBIE", "MOVETYPE_VPHYSICS", "SND_IGNORE_PHONEMES", "ACT_GESTURE_RANGE_ATTACK_SHOTGUN", "ACT_MP_RUN", "ACT_DOD_CROUCH_IDLE_BAZOOKA", "ACT_MP_GESTURE_VC_HANDMOUTH_BUILDING", "GMOD_CHANNEL_PLAYING", "DRGBPicker", "MOVETYPE_NONE", "CreateSound", "ACT_MP_CROUCHWALK_PRIMARY", "ACT_DOD_RUN_AIM_BAR", "MOVECOLLIDE_COUNT", "COLLISION_GROUP_IN_VEHICLE", "collectgarbage", "ACT_DI_ALYX_ZOMBIE_SHOTGUN64", "DBinder", "MATERIAL_RT_DEPTH_NONE", "ACT_GRENADE_TOSS", "ACT_VM_IDLE_DEPLOYED", "ACT_VM_DOWN", "ACT_SHOTGUN_IDLE4", "DOF_SPACING", "ACT_DOD_STAND_IDLE_30CAL", "steamworks", "MATERIAL_RT_DEPTH_SEPARATE", "MATERIAL_LIGHT_SPOT", "EFL_CHECK_UNTOUCH", "GO_NORTH", "DProperty_Float", "IN_ALT1", "MATERIAL_LIGHT_DISABLE", "SCHED_RELOAD", "isangle", "ACT_DI_ALYX_ZOMBIE_TORSO_MELEE", "ACT_HL2MP_SIT_SMG1", "MATERIAL_FOG_NONE", "HULL_SMALL_CENTERED", "ACT_DOD_DEPLOY_TOMMY", "MATERIAL_CULLMODE_CW", "SURF_NOCHOP", "TRACER_LINE_AND_WHIZ", "SCHED_INTERACTION_WAIT_FOR_PARTNER", "sql", "MATERIAL_POLYGON", "MATERIAL_POINTS", "IsValid", "ACT_VM_HAULBACK", "ACT_MP_WALK_SECONDARY", "ACT_WALK_AIM_SHOTGUN", "FCVAR_SERVER_CAN_EXECUTE", "MAT_DEFAULT", "MAT_WOOD", "ACT_MP_PRIMARY_GRENADE2_IDLE", "ACT_SIGNAL_HALT", "ACT_HL2MP_SWIM_IDLE_MAGIC", "ACT_MP_ATTACK_SWIM_SECONDARYFIRE", "ACT_MP_GESTURE_VC_NODNO_BUILDING", "ACT_COVER", "SCHED_PATROL_WALK", "ACT_HL2MP_SWIM_IDLE_SLAM", "MAT_FLESH", "MAT_EGGSHELL", "KEY_NUMLOCK", "CLuaLocomotion", "ACT_DOD_PRIMARYATTACK_TOMMY", "ACT_DOD_CROUCH_AIM_RIFLE", "MAT_CONCRETE", "ACT_VM_DOWN_EMPTY", "ACT_WALK_AIM", "ACT_DOD_WALK_AIM_MP40", "MAT_CLIP", "CT_REFUGEE", "MAT_ALIENFLESH", "MASK_WATER", "MASK_VISIBLE_AND_NPCS", "MASK_VISIBLE", "ACT_HL2MP_WALK_CROUCH_ZOMBIE_03", "MASK_SPLITAREAPORTAL", "ACT_IDLE_SUITCASE", "MASK_SOLID_BRUSHONLY", "MASK_SOLID", "ACT_DOD_ZOOMLOAD_PRONE_BAZOOKA", "GMOD_CHANNEL_STALLED", "ACT_DOD_PRONE_FORWARD_ZOOMED", "MASK_SHOT_PORTAL", "ACT_DOD_DEPLOYED", "MASK_SHOT_HULL", "MASK_PLAYERSOLID_BRUSHONLY", "ACT_HL2MP_IDLE_CROUCH_RPG", "game", "SCHED_COMBAT_SWEEP", "EffectData", "MASK_OPAQUE", "ACT_LAND", "KEY_NUMLOCKTOGGLE", "COND_HEAR_MOVE_AWAY", "MASK_NPCSOLID", "ACT_COMBAT_IDLE", "MASK_DEADSOLID", "MASK_CURRENT", "OrderVectors", "DMG_BULLET", "COND_SEE_PLAYER", "ACT_HL2MP_SIT_AR2", "ACT_HL2MP_IDLE_MELEE_ANGRY", "KEY_P", "MASK_ALL", "ACT_GESTURE_TURN_LEFT90", "kRenderFxGlowShell", "kRenderFxExplode", "kRenderFxDistort", "kRenderFxNoDissipation", "ACT_VM_DRAW_SILENCED", "kRenderFxFlickerFast", "kRenderFxFlickerSlow", "EFL_BOT_FROZEN", "COND_PLAYER_ADDED_TO_SQUAD", "DColorCombo", "kRenderFxStrobeFast", "kRenderFxSolidFast", "kRenderFxSolidSlow", "kRenderFxFadeFast", "ACT_VM_PRIMARYATTACK_DEPLOYED_2", "kRenderFxFadeSlow", "OnModelLoaded", "kRenderFxPulseFastWide", "kRenderFxPulseFast", "KEY_TAB", "kRenderFxNone", "ACT_MP_ATTACK_STAND_MELEE_SECONDARY", "ACT_MP_ATTACK_SWIM_MELEE", "IN_GRENADE1", "ACT_MP_GESTURE_VC_NODNO_MELEE", "ACT_GESTURE_FLINCH_HEAD", "ACT_DOD_CROUCHWALK_IDLE_TNT", "ClientsideRagdoll", "debugoverlay", "IN_SPEED", "IN_SCORE", "CHAN_BODY", "ACT_DOD_SPRINT_IDLE_MG", "IN_RELOAD", "ACT_MP_ATTACK_CROUCH_SECONDARYFIRE", "ACT_DOD_RUN_AIM_BAZOOKA", "IN_RUN", "IN_MOVELEFT", "IsEnemyEntityName", "IN_LEFT", "ACT_WALK_RELAXED", "ACT_DOD_RELOAD_FG42", "IN_CANCEL", "ACT_HL2MP_SWIM_SLAM", "ACT_RUN_ON_FIRE", "ACT_SMG2_TOAUTO", "COND_NOT_FACING_ATTACK", "numpad", "ACT_SLAM_STICKWALL_ND_ATTACH", "ACT_MP_RUN_PRIMARY", "ismatrix", "IN_FORWARD", "IN_DUCK", "ACT_DOD_SPRINT_IDLE_PSCHRECK", "DamageInfo", "ACT_WALK_CROUCH_RPG", "JOYSTICK_FIRST_AXIS_BUTTON", "DListViewLine", "ACT_VM_RELOAD_M203", "ACT_DOD_PRIMARYATTACK_SPADE", "ACT_TURNRIGHT45", "FL_STEPMOVEMENT", "IMAGE_FORMAT_BGR888", "RT_SIZE_FULL_FRAME_BUFFER_ROUNDED_UP", "IMAGE_FORMAT_DEFAULT", "SF_CITIZEN_MEDIC", "HULL_LARGE_CENTERED", "ACT_DOD_ZOOMLOAD_PRONE_PSCHRECK", "HULL_LARGE", "HULL_TINY_CENTERED", "HULL_MEDIUM", "STENCIL_LESS", "ACT_HL2MP_GESTURE_RANGE_ATTACK_ZOMBIE", "ACT_MP_GESTURE_VC_NODYES_MELEE", "ACT_MP_ATTACK_AIRWALK_GRENADE", "HUD_PRINTTALK", "ACT_IDLE_SMG1_STIMULATED", "HUD_PRINTCONSOLE", "CONTENTS_MONSTER", "ACT_VM_UNDEPLOY", "ACT_MP_JUMP_START_BUILDING", "FORCE_NUMBER", "ACT_MP_RELOAD_SWIM_PRIMARY", "ACT_VM_DRAW", "ACT_VM_IIN", "ACT_VM_HITLEFT2", "ACT_HL2MP_IDLE_CROUCH", "HITGROUP_RIGHTARM", "ACT_WALK_RIFLE_RELAXED", "DMG_DISSOLVE", "HITGROUP_GENERIC", "SF_NPC_NO_PLAYER_PUSHAWAY", "ACT_RANGE_ATTACK_HMG1", "GMOD_CHANNEL_STOPPED", "GLOBAL_DEAD", "DrawSobel", "GLOBAL_ON", "PLAYER_LEAVE_AIMING", "KEY_Z", "DCheckBox", "SCHED_PRE_FAIL_ESTABLISH_LINE_OF_FIRE", "GESTURE_SLOT_VCD", "ACT_HL2MP_RUN_PASSIVE", "GESTURE_SLOT_FLINCH", "ACT_HL2MP_WALK_CROSSBOW", "ACT_DOD_RELOAD_DEPLOYED_BAR", "MOUSE_RIGHT", "ACT_MP_STAND_BUILDING", "timer", "ACT_RAPPEL_LOOP", "newproxy", "GESTURE_SLOT_GRENADE", "ACT_DOD_CROUCH_AIM_BOLT", "FVPHYSICS_PART_OF_RAGDOLL", "ACT_HL2MP_IDLE_CROUCH_SLAM", "ACT_MP_ATTACK_CROUCH_PRIMARYFIRE_DEPLOYED", "ACT_GESTURE_TURN_LEFT45_FLAT", "ACT_DOD_CROUCH_AIM_KNIFE", "COND_SCHEDULE_DONE", "ACT_BUSY_LEAN_LEFT_EXIT", "FVPHYSICS_NO_SELF_COLLISIONS", "FVPHYSICS_NO_PLAYER_PICKUP", "FVPHYSICS_MULTIOBJECT_ENTITY", "FSOLID_MAX_BITS", "istable", "ACT_VM_RELOAD_IDLE", "COLLISION_GROUP_INTERACTIVE_DEBRIS", "STENCILCOMPARISONFUNCTION_ALWAYS", "ACT_RUN_CROUCH_AIM", "FSOLID_TRIGGER", "FSOLID_NOT_SOLID", "DOF_Kill", "ACT_VM_ATTACH_SILENCER", "EFL_NO_THINK_FUNCTION", "ACT_180_LEFT", "FSOLID_CUSTOMBOXTEST", "FSOLID_CUSTOMRAYTEST", "FORCE_BOOL", "FCVAR_DONTRECORD", "ACT_MP_GESTURE_FLINCH_LEFTLEG", "FL_TRANSRAGDOLL", "ACT_RUN_RELAXED", "kRenderFxClampMinScale", "FL_FROZEN", "FL_KILLME", "FL_WORLDBRUSH", "ACT_CROUCHING_SHIELD_ATTACK", "PrecacheParticleSystem", "ACT_RUN_STIMULATED", "FL_BASEVELOCITY", "FL_GRAPHED", "ACT_GESTURE_BIG_FLINCH", "FL_PARTIALGROUND", "FL_AIMTARGET", "ACT_DOD_CROUCHWALK_ZOOM_BAZOOKA", "FL_NOTARGET", "ACT_DOD_HS_IDLE_STICKGRENADE", "ACT_RANGE_ATTACK_SNIPER_RIFLE", "ACT_HL2MP_JUMP", "ACT_DOD_WALK_IDLE_PISTOL", "Localize", "BOTTOM", "pairs", "FL_INWATER", "FL_FAKECLIENT", "FL_CLIENT", "ACT_VM_DEPLOY_6", "ACT_HL2MP_WALK_PISTOL", "ACT_DIE_CHESTSHOT", "FL_ATCONTROLS", "FL_ONFIRE", "ACT_DOD_RELOAD_MP44", "ACT_MP_ATTACK_AIRWALK_GRENADE_MELEE", "ACT_GESTURE_FLINCH_RIGHTLEG", "FL_ONTRAIN", "http", "ACT_HL2MP_SWIM_DUEL", "FL_ANIMDUCKING", "FL_DUCKING", "MarkupObject", "FFT_32768", "CompileString", "COLLISION_GROUP_PROJECTILE", "MOUSE_WHEEL_DOWN", "ENT_NEXTBOT", "COLLISION_GROUP_PLAYER", "FFT_16384", "FFT_8192", "KEY_PAD_6", "FFT_4096", "ACT_DOD_RELOAD_PRONE_PISTOL", "SENSORBONE", "ACT_DOD_WALK_AIM_GREASE", "FFT_256", "KEY_FIRST", "FCVAR_UNREGISTERED", "ACT_DOD_PRONEWALK_IDLE_BAR", "ACT_DOD_WALK_AIM_PISTOL", "ACT_SMG2_TOBURST", "FCVAR_UNLOGGED", "FCVAR_SERVER_CANNOT_QUERY", "FCVAR_REPLICATED", "FCVAR_PROTECTED", "COND_NO_PRIMARY_AMMO", "FCVAR_NOT_CONNECTED", "ACT_HANDGRENADE_THROW1", "ACT_SLAM_STICKWALL_ND_IDLE", "ACT_MP_ATTACK_STAND_PREFIRE", "FCVAR_NEVER_AS_STRING", "string", "ACT_WALK_RIFLE_STIMULATED", "FCVAR_LUA_SERVER", "FCVAR_LUA_CLIENT", "FCVAR_DEMO", "FCVAR_CLIENTDLL", "STENCIL_GREATER", "FCVAR_CHEAT", "TYPE_USERDATA", "Vector", "EFL_USE_PARTITION_WHEN_NOT_SOLID", "EFL_SETTING_UP_BONES", "ACT_BUSY_LEAN_BACK", "sound", "ACT_DOD_PRONE_DEPLOY_RIFLE", "DListViewLabel", "KEY_XBUTTON_DOWN", "ACT_HL2MP_IDLE_ANGRY", "ACT_IDLE_MELEE", "CONTENTS_CURRENT_180", "CLASS_COMBINE_HUNTER", "ACT_DOD_PRIMARYATTACK_PRONE_RIFLE", "DrawMaterialOverlay", "ACT_DOD_PRIMARYATTACK_RIFLE", "EFL_NO_PHYSCANNON_INTERACTION", "EFL_NO_MEGAPHYSCANNON_RAGDOLL", "EFL_NO_DISSOLVE", "EFL_NO_AUTO_EDICT_ATTACH", "COND_SMELL", "EFL_NOTIFY", "KEY_K", "EFL_IS_BEING_LIFTED_BY_BARNACLE", "duplicator", "EFL_IN_SKYBOX", "motionsensor", "KEY_PAD_MINUS", "ACT_HL2MP_SWIM_IDLE_RPG", "EFL_DONTBLOCKLOS", "ACT_VM_PICKUP", "TypeID", "EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS", "EFL_DIRTY_SPATIAL_PARTITION", "EFL_DIRTY_SHADOWUPDATE", "ACT_HL2MP_JUMP_SUITCASE", "EFL_DIRTY_ABSTRANSFORM", "Sound", "ACT_MP_ATTACK_AIRWALK_SECONDARY", "AddWorldTip", "MATERIAL_LIGHT_DIRECTIONAL", "DynamicLight", "kRenderFxStrobeFaster", "EF_FOLLOWBONE", "VisualizeLayout", "ACT_HL2MP_SWIM_IDLE_DUEL", "ACT_DOD_PRONE_AIM_KNIFE", "MOVETYPE_PUSH", "ACT_DOD_RELOAD_CROUCH_M1CARBINE", "WEAPON_PROFICIENCY_POOR", "BONE_USED_BY_HITBOX", "PLAYER_START_AIMING", "ACT_TURN", "ACT_MP_ATTACK_SWIM_POSTFIRE", "Slider", "KEY_0", "ACT_HL2MP_WALK_CROUCH_CAMERA", "FL_NPC", "TOP", "ACT_GMOD_GESTURE_AGREE", "RIGHT", "DFrame", "LEFT", "FILL", "DMG_SLOWBURN", "BLOOD_COLOR_ANTLION", "KEY_F3", "ACT_DOD_RUN_IDLE_TOMMY", "SNDLVL_70dB", "PATTACH_WORLDORIGIN", "ACT_STEP_BACK", "DMG_PLASMA", "ACT_GMOD_GESTURE_RANGE_ZOMBIE", "DMG_PHYSGUN", "ACT_RUN_AIM_AGITATED", "OBS_MODE_ROAMING", "AddCSLuaFile", "HITGROUP_HEAD", "DMG_BUCKSHOT", "DMG_AIRBOAT", "ACT_VM_RECOIL1", "SCHED_FAIL_TAKE_COVER", "ACT_DOD_CROUCH_IDLE_PSCHRECK", "DMG_ALWAYSGIB", "Frame", "DMG_ENERGYBEAM", "ACT_HL2MP_GESTURE_RELOAD_DUEL", "SNDLVL_95dB", "SetGlobalFloat", "ACT_WALK_CARRY", "ACT_GESTURE_MELEE_ATTACK2", "JS_Language", "ACT_VM_PRIMARYATTACK_DEPLOYED_5", "ACT_DOD_PRONE_AIM_GREN_FRAG", "SCHED_ESTABLISH_LINE_OF_FIRE", "ACT_DOD_WALK_AIM_BAR", "ACT_SLAM_TRIPMINE_TO_THROW_ND", "STUDIO_SHADOWDEPTHTEXTURE", "ACT_HL2MP_GESTURE_RELOAD_FIST", "DMG_SLASH", "ACT_DOD_HS_IDLE", "ACT_MP_ATTACK_STAND_POSTFIRE", "DMG_GENERIC", "ACT_HL2MP_SWIM_SHOTGUN", "ACT_VM_DEPLOY_1", "D_LI", "CAP_DUCK", "ACT_GESTURE_TURN_LEFT90_FLAT", "FindTooltip", "D_FR", "ACT_DOD_RELOAD_PRONE_FG42", "KEY_EQUAL", "RealFrameTime", "WorkshopFileBase", "D_ER", "ACT_DOD_WALK_AIM_30CAL", "ACT_HL2MP_SWIM_IDLE_ZOMBIE", "ACT_DOD_PRIMARYATTACK_CROUCH_GREN_STICK", "CT_REBEL", "MAT_BLOODYFLESH", "ACT_HL2MP_IDLE_REVOLVER", "DCheckBoxLabel", "KEY_9", "CREATERENDERTARGETFLAGS_UNFILTERABLE_OK", "ACT_MP_SECONDARY_GRENADE1_ATTACK", "EFL_NOCLIP_ACTIVE", "ACT_HL2MP_RUN_CAMERA", "FL_GODMODE", "EmitSentence", "CONTENTS_TRANSLUCENT", "ACT_DOD_HS_CROUCH_30CAL", "ACT_DOD_PRONE_AIM_RIFLE", "ACT_GMOD_GESTURE_ITEM_GIVE", "CONTENTS_ORIGIN", "HITGROUP_GEAR", "CONTENTS_LADDER", "ACT_DOD_WALK_AIM_RIFLE", "CONTENTS_HITBOX", "CONTENTS_DEBRIS", "ACT_DOD_PRONE_AIM_MG", "CONTENTS_CURRENT_UP", "ACT_PHYSCANNON_ANIMATE_POST", "ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE", "ACT_DISARM", "CONTENTS_CURRENT_90", "ACT_MP_RELOAD_STAND_PRIMARY_LOOP", "CONTENTS_CURRENT_270", "TEXTUREFLAGS_UNUSED_00400000", "CONTENTS_PLAYERCLIP", "Error", "ACT_MP_SECONDARY_GRENADE1_DRAW", "SNDLVL_60dB", "ACT_VM_RELOAD", "CAP_AUTO_DOORS", "ACT_DI_ALYX_ZOMBIE_SHOTGUN26", "CONTENTS_TEAM2", "CONTENTS_TEAM1", "COLLISION_GROUP_NPC_SCRIPTED", "CONTENTS_OPAQUE", "CONTENTS_BLOCKLOS", "ACT_VM_DIFIREMODE", "ACT_DOD_CROUCH_AIM_GREN_FRAG", "ACT_HL2MP_WALK_CROUCH_FIST", "CONTENTS_AUX", "ACT_DOD_DEPLOY_MG", "ACT_MP_CROUCH_DEPLOYED", "ACT_DOD_WALK_IDLE_30CAL", "CONTENTS_EMPTY", "SCHED_MELEE_ATTACK1", "ACT_90_RIGHT", "ACT_GESTURE_RANGE_ATTACK_SMG1", "COND_WEAPON_BLOCKED_BY_FRIEND", "TEXTUREFLAGS_UNUSED_40000000", "ACT_HL2MP_SWIM_SCARED", "ACT_DOD_PRONE_ZOOM_FORWARD_BAZOOKA", "COND_TOO_CLOSE_TO_ATTACK", "COND_TASK_FAILED", "ACT_POLICE_HARASS1", "ACT_STRAFE_LEFT", "DPanelList", "VGUIFrameTime", "error", "COND_TALKER_RESPOND_TO_QUESTION", "ACT_DOD_RELOAD_DEPLOYED", "ACT_DOD_SECONDARYATTACK_PRONE_MP40", "MASK_BLOCKLOS", "ACT_MP_SECONDARY_GRENADE2_DRAW", "ACT_GESTURE_TURN_RIGHT45_FLAT", "ACT_CROUCHING_GRENADEREADY", "COND_SEE_DISLIKE", "COND_RECEIVED_ORDERS", "ITexture", "COND_PLAYER_REMOVED_FROM_SQUAD", "COND_PLAYER_PUSHING", "ACT_DOD_PRIMARYATTACK_DEPLOYED_RIFLE", "ACT_VM_DEPLOYED_IDLE", "COND_NPC_FREEZE", "SCHED_NPC_FREEZE", "COND_NO_SECONDARY_AMMO", "COND_MOBBED_BY_ENEMIES", "COND_LOST_PLAYER", "COLLISION_GROUP_INTERACTIVE", "EF_BONEMERGE", "ACT_MP_SWIM_PDA", "DAdjustableModelPanel", "ACT_VM_FIRE_TO_EMPTY", "tonumber", "ACT_UNDEPLOY", "ACT_VM_SWINGMISS", "COND_IN_PVS", "BLEND_ONE_MINUS_DST_COLOR", "COND_IDLE_INTERRUPT", "JOYSTICK_FIRST_BUTTON", "COND_HEAVY_DAMAGE", "RENDERMODE_ENVIROMENTAL", "RunString", "COND_HEAR_SPOOKY", "ACT_MP_ATTACK_SWIM_GRENADE_MELEE", "CSoundPatch", "COND_HEAR_PHYSICS_DANGER", "ACT_WALK_STIMULATED", "COLLISION_GROUP_BREAKABLE_GLASS", "MASK_NPCSOLID_BRUSHONLY", "COND_GIVE_WAY", "ACT_GMOD_IN_CHAT", "BONE_USED_BY_BONE_MERGE", "RunConsoleCommand", "ACT_HL2MP_IDLE_CROUCH_PHYSGUN", "COND_ENEMY_WENT_NULL", "DMG_PREVENT_PHYSICS_FORCE", "ACT_DEEPIDLE4", "COND_ENEMY_TOO_FAR", "COND_CAN_RANGE_ATTACK1", "KEY_RSHIFT", "CONTENTS_TEAM4", "ACT_DOD_HS_IDLE_KNIFE", "EditablePanel", "ACT_HL2MP_SWIM_MAGIC", "GetHUDPanel", "ACT_COVER_PISTOL_LOW", "IsEntity", "COLLISION_GROUP_NPC_ACTOR", "SCHED_CHASE_ENEMY_FAILED", "COLLISION_GROUP_DISSOLVING", "ACT_HL2MP_GESTURE_RANGE_ATTACK_SUITCASE", "ACT_FLY", "COLLISION_GROUP_DOOR_BLOCKER", "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED", "ACT_HL2MP_RUN_SUITCASE", "ACT_HL2MP_GESTURE_RELOAD_ZOMBIE", "CLASS_BARNACLE", "DSizeToContents", "ACT_DOD_HS_IDLE_MG42", "ACT_VM_RELOAD2", "ACT_HL2MP_RUN_ZOMBIE", "ACT_DOD_CROUCHWALK_IDLE_30CAL", "ACT_DOD_PRONE_AIM_BAR", "COLLISION_GROUP_PLAYER_MOVEMENT", "FSOLID_USE_TRIGGER_BOUNDS", "ACT_CROUCHING_SHIELD_KNOCKBACK", "EF_NOINTERP", "COLLISION_GROUP_DEBRIS", "ACT_VM_SWINGHIT", "ACT_DOD_WALK_IDLE_MG", "ColorRand", "ACT_DOD_RELOAD_CROUCH_PSCHRECK", "CLASS_EARTH_FAUNA", "CLASS_FLARE", "CLASS_MISSILE", "CLASS_STALKER", "ACT_DOD_RUN_AIM_SPADE", "ACT_VM_HOLSTER_EMPTY", "ACT_BIG_FLINCH", "CLASS_MANHACK", "CLASS_HEADCRAB", "ACT_RANGE_AIM_SMG1_LOW", "CLASS_COMBINE_GUNSHIP", "SOLID_VPHYSICS", "CLASS_CITIZEN_REBEL", "CLASS_CITIZEN_PASSIVE", "ACT_DOD_PRIMARYATTACK_PRONE_KNIFE", "CAP_USE_WEAPONS", "MOVECOLLIDE_FLY_SLIDE", "DermaMenu", "STUDIO_WIREFRAME", "MOUSE_5", "ACT_VM_FIDGET", "draw", "CHAN_STATIC", "ACT_DOD_STAND_IDLE_BAR", "CHAN_STREAM", "ACT_DOD_PRIMARYATTACK_30CAL", "CHAN_ITEM", "KEY_XBUTTON_LTRIGGER", "GetLoadPanel", "ACT_MP_RELOAD_SWIM_SECONDARY_LOOP", "ACT_DOD_HS_IDLE_PSCHRECK", "ACT_HL2MP_IDLE_CROUCH_PISTOL", "ACT_RELOAD_SMG1", "BUTTON_CODE_COUNT", "ACT_TURN_LEFT", "ACT_DOD_RELOAD_M1CARBINE", "CAP_NO_HIT_SQUADMATES", "CAP_AIM_GUN", "PATTACH_CUSTOMORIGIN", "CAP_ANIMATEDFACE", "CAP_INNATE_MELEE_ATTACK2", "STENCIL_KEEP", "ACT_DOD_STAND_AIM_KNIFE", "CancelLoading", "ACT_VM_SPRINT_ENTER", "ACT_HL2MP_IDLE_CAMERA", "Path", "CAP_WEAPON_RANGE_ATTACK2", "ACT_HL2MP_GESTURE_RELOAD", "CAP_TURN_HEAD", "ACT_MP_JUMP_START", "CONTENTS_IGNORE_NODRAW_OPAQUE", "ACT_RANGE_ATTACK_SHOTGUN_LOW", "CAP_SKIP_NAV_GROUND_CHECK", "ACT_MP_GESTURE_VC_NODYES_SECONDARY", "RENDERMODE_TRANSCOLOR", "CAP_MOVE_CLIMB", "CAP_MOVE_FLY", "CAP_MOVE_JUMP", "IVideoWriter", "JOYSTICK_LAST", "KEY_XBUTTON_RTRIGGER", "cleanup", "IN_JUMP", "LanguageChanged", "ACT_DOD_HS_CROUCH_PISTOL", "DTree", "RegisterDermaMenuForClose", "JOYSTICK_FIRST_POV_BUTTON", "MOUSE_COUNT", "MOUSE_LAST", "MOUSE_WHEEL_UP", "MOUSE_4", "MOUSE_MIDDLE", "KEY_XSTICK2_UP", "KEY_XSTICK2_LEFT", "JOYSTICK_LAST_AXIS_BUTTON", "module", "ACT_OBJ_PLACING", "ACT_MP_JUMP_PDA", "KEY_XSTICK1_UP", "Color", "ACT_DIE_RIGHTSIDE", "ACT_MP_AIRWALK", "KEY_XSTICK1_LEFT", "CloseDermaMenus", "KEY_XSTICK1_RIGHT", "ACT_HL2MP_WALK_SMG1", "FFT_1024", "ACT_DOD_SECONDARYATTACK_PRONE", "ACT_VM_HOLSTER_M203", "KEY_XBUTTON_STICK1", "KEY_XBUTTON_START", "KEY_XBUTTON_BACK", "ACT_DOD_STAND_AIM_C96", "KEY_XBUTTON_RIGHT_SHOULDER", "KEY_7", "KEY_COUNT", "ACT_GMOD_GESTURE_BOW", "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED_30CAL", "DVScrollBar", "GetDownloadables", "KEY_LAST", "ACT_HL2MP_SWIM_IDLE_MELEE2", "MASK_NPCWORLDSTATIC", "ACT_DOD_RELOAD_GREASEGUN", "TEXTUREFLAGS_DEPTHRENDERTARGET", "KEY_F10", "ACT_DOD_WALK_IDLE", "KEY_F8", "KEY_F6", "xpcall", "ACT_MP_AIRWALK_MELEE", "KEY_F5", "KEY_F4", "KEY_APP", "ACT_RANGE_ATTACK_SMG1", "BONE_USED_BY_VERTEX_LOD2", "DMG_REMOVENORAGDOLL", "ACT_MP_MELEE_GRENADE1_IDLE", "ACT_DOD_STAND_AIM_PSCHRECK", "KEY_F1", "KEY_RIGHT", "KEY_DOWN", "KEY_RWIN", "ACT_VM_DRYFIRE_SILENCED", "RichText", "ACT_HL2MP_SWIM_PHYSGUN", "ACT_CROUCHING_PRIMARYATTACK", "ACT_DOD_PRONE_AIM_MP40", "BONE_PHYSICS_PROCEDURAL", "ProtectedCall", "KEY_RCONTROL", "ACT_VM_IDLE_DEPLOYED_3", "KEY_RALT", "LAST_SHARED_COLLISION_GROUP", "KEY_LSHIFT", "ACT_HL2MP_SWIM_MELEE2", "KEY_PAGEUP", "ACT_DOD_CROUCH_AIM_TOMMY", "KEY_INSERT", "KEY_SCROLLLOCK", "KEY_ESCAPE", "ACT_HL2MP_IDLE_CROUCH_ANGRY", "kRenderFxPulseSlow", "KEY_MINUS", "KEY_BACKSLASH", "ACT_LEAP", "ACT_RUN_AIM", "DrawColorModify", "UTIL_IsUselessModel", "EFL_HAS_PLAYER_CHILD", "GetRenderTarget", "KEY_PAD_DIVIDE", "ACT_RUN_RIFLE_STIMULATED", "ACT_BUSY_STAND", "KEY_PAD_5", "KEY_PAD_4", "TauntCamera", "SND_STOP_LOOPING", "ACT_SMG2_DRYFIRE2", "ACT_HL2MP_RUN_GRENADE", "debug", "KEY_X", "GO_JUMP", "KEY_S", "KEY_R", "ACT_SIGNAL_GROUP", "CREATERENDERTARGETFLAGS_AUTOMIPMAP", "KEY_G", "CAP_WEAPON_MELEE_ATTACK1", "KEY_A", "ACT_DRIVE_JEEP", "ACT_DOD_PRIMARYATTACK_PRONE_TOMMY", "KEY_8", "KEY_XBUTTON_Y", "BONE_USED_MASK", "KEY_6", "KEY_5", "KEY_3", "COND_LIGHT_DAMAGE", "ACT_HL2MP_IDLE_SCARED", "BUTTON_CODE_LAST", "BUTTON_CODE_NONE", "BUTTON_CODE_INVALID", "BOX_BOTTOM", "ACT_MP_SWIM_DEPLOYED_PRIMARY", "ACT_DOD_PRIMARYATTACK_C96", "LerpVector", "ACT_DOD_RELOAD_CROUCH_TOMMY", "BOX_FRONT", "BONE_USED_BY_ANYTHING", "BONE_USED_BY_VERTEX_LOD7", "ACT_VM_DEPLOY_4", "assert", "ACT_DOD_PRIMARYATTACK_PRONE_BAZOOKA", "ACT_DOD_PRIMARYATTACK_MP44", "BONE_USED_BY_VERTEX_LOD6", "BONE_USED_BY_VERTEX_LOD5", "BONE_USED_BY_VERTEX_LOD3", "BONE_USED_BY_VERTEX_LOD1", "ACT_DIESIMPLE", "BONE_USED_BY_VERTEX_LOD0", "ACT_MP_RELOAD_CROUCH_END", "BONE_USED_BY_ATTACHMENT", "SysTime", "KEY_LWIN", "ACT_CROUCHIDLE_STIMULATED", "ACT_MP_CROUCHWALK_MELEE", "menubar", "BLOOD_COLOR_ANTLION_WORKER", "GMOD_CHANNEL_PAUSED", "ACT_HL2MP_IDLE_CROUCH_SCARED", "BLOOD_COLOR_YELLOW", "DONT_BLEED", "ents", "ACT_DOD_CROUCH_ZOOM_PSCHRECK", "ACT_DOD_WALK_AIM_BOLT", "BLOOD_COLOR_RED", "GetConVar", "BLEND_SRC_COLOR", "BLEND_SRC_ALPHA_SATURATE", "BLEND_ONE_MINUS_DST_ALPHA", "BLEND_DST_ALPHA", "ACT_VM_IDLE_5", "ACT_FLINCH_RIGHTARM", "ACT_FIRE_END", "BLEND_ONE", "LAST_SHARED_ACTIVITY", "ACT_GMOD_GESTURE_MELEE_SHOVE_2HAND", "ACT_MP_GESTURE_FLINCH", "DNumberWang", "ACT_HL2MP_RUN_CROSSBOW", "ACT_GMOD_GESTURE_ITEM_DROP", "ACT_HL2MP_ZOMBIE_SLUMP_IDLE", "BLEND_ONE_MINUS_SRC_ALPHA", "MATERIAL_RT_DEPTH_ONLY", "ACT_DOD_PRIMARYATTACK_CROUCH_SPADE", "ACT_HL2MP_SIT_MELEE", "ACT_SLAM_THROW_IDLE", "ACT_HL2MP_WALK_MAGIC", "GetLoadStatus", "ACT_DOD_CROUCHWALK_AIM_MP44", "ACT_HL2MP_SIT_GRENADE", "ACT_HL2MP_RUN_REVOLVER", "ACT_DOD_STAND_AIM_GREN_STICK", "RenderDoF", "ACT_HL2MP_SIT_SHOTGUN", "ACT_HL2MP_SIT_PISTOL", "KEY_BREAK", "ACT_HL2MP_GESTURE_RELOAD_MELEE2", "ACT_FIRE_START", "ACT_HL2MP_WALK_MELEE2", "ACT_HL2MP_IDLE_MELEE2", "DHTMLControls", "ACT_HL2MP_GESTURE_RELOAD_PASSIVE", "ACT_HL2MP_GESTURE_RANGE_ATTACK_PASSIVE", "COLLISION_GROUP_PASSABLE_DOOR", "PanelList", "ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE", "ACT_DOD_PRIMARYATTACK_MG", "ACT_HL2MP_RUN_KNIFE", "ACT_HL2MP_IDLE_KNIFE", "ACT_HL2MP_WALK_DUEL", "ACT_DOD_PRONE_AIM_30CAL", "ACT_HL2MP_SIT", "ACT_HL2MP_SWIM_FIST", "gui", "Lerp", "ACT_SLAM_STICKWALL_TO_TRIPMINE_ND", "ACT_HL2MP_JUMP_FIST", "ACT_DIERAGDOLL", "umsg", "DropEntityIfHeld", "ACT_DOD_STAND_IDLE_BAZOOKA", "ACT_HL2MP_JUMP_GRENADE", "ACT_GESTURE_TURN_RIGHT", "ACT_MP_GESTURE_FLINCH_RIGHTARM", "notification", "ACT_HL2MP_RUN", "ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST", "CONTENTS_SLIME", "STNDRD", "ACT_HL2MP_RUN_FIST", "DColumnSheet", "ACT_DOD_CROUCH_IDLE_RIFLE", "ACT_HL2MP_WALK_PASSIVE", "ACT_DOD_STAND_AIM_BAZOOKA", "SCHED_BIG_FLINCH", "ACT_VM_HITCENTER", "ACT_DOD_STAND_IDLE_BOLT", "ACT_DOD_WALK_IDLE_C96", "ACT_DOD_WALK_IDLE_BOLT", "DIconBrowser", "ACT_HL2MP_RUN_AR2", "ACT_MP_VCD", "ACT_HL2MP_IDLE_FIST", "ACT_DOD_WALK_AIM_C96", "ACT_VM_MISSLEFT2", "ACT_GMOD_NOCLIP_LAYER", "Angle", "GO_LADDER_UP", "ACT_HL2MP_IDLE_CROUCH_CROSSBOW", "ACT_READINESS_RELAXED_TO_STIMULATED", "ACT_MP_RELOAD_STAND_PRIMARY_END", "ACT_180_RIGHT", "ACT_DOD_CROUCH_ZOOM_RIFLE", "ACT_IDLE_AIM_STIMULATED", "ACT_VM_RELOAD_INSERT_EMPTY", "ACT_DOD_RUN_AIM_PSCHRECK", "Msg", "TEXTUREFLAGS_NORMAL", "ACT_PRONE_IDLE", "ACT_HL2MP_SIT_PHYSGUN", "ACT_RUN_AGITATED", "DModelPanel", "OpenFolder", "RENDERMODE_NONE", "ACT_VM_IFIREMODE", "ACT_POLICE_HARASS2", "gamemode", "ACT_WALK_AIM_RELAXED", "CLASS_ANTLION", "ACT_DOD_RELOAD_PRONE_DEPLOYED", "ACT_MP_JUMP_FLOAT_MELEE", "ACT_MP_GESTURE_FLINCH_MELEE", "SNDLVL_20dB", "ACT_VM_READY_M203", "ACT_HL2MP_SWIM_RPG", "ACT_GMOD_GESTURE_ITEM_PLACE", "weapons", "ACT_IDLE_AIM_AGITATED", "TEXTUREFLAGS_ENVMAP", "ACT_VM_SHOOTLAST", "ACT_DOD_CROUCH_IDLE_GREASE", "GLOBAL_OFF", "ParticleEmitter", "VGUIRect", "ACT_OVERLAY_SHIELD_DOWN", "ACT_VM_ISHOOT_LAST", "DComboBox", "ACT_VM_FIREMODE", "ACT_VM_IRECOIL2", "ACT_VM_IIDLE_EMPTY", "ACT_DOD_STAND_AIM_TOMMY", "KEY_Y", "ACT_VM_IDLE_LOWERED", "ACT_HL2MP_SWIM_IDLE_CAMERA", "DMenuOption", "ACT_VM_DEPLOYED_LIFTED_OUT", "ACT_VM_DEPLOYED_LIFTED_IN", "ACT_DOD_CROUCHWALK_IDLE_MP40", "ACT_DOD_STAND_AIM_PISTOL", "ACT_PLAYER_IDLE_FIRE", "ACT_VM_DEPLOYED_IRON_DRYFIRE", "ACT_VM_DEPLOYED_IRON_FIRE", "ACT_VM_DEPLOYED_IRON_IDLE", "PhysObj", "isnumber", "ACT_VM_DEPLOYED_RELOAD_EMPTY", "ACT_RUN_AIM_STEALTH_PISTOL", "ACT_HL2MP_RUN_PANICKED", "ACT_VM_DEPLOYED_RELOAD", "ACT_VM_DEPLOYED_DRYFIRE", "ACT_VM_DEPLOYED_FIRE", "ACT_VM_HOLSTER", "COND_PHYSICS_DAMAGE", "ACT_TURNLEFT45", "ACT_DOD_HS_CROUCH_STICKGRENADE", "ACT_VM_HITKILL", "TGAImage", "RenderAngles", "ACT_RUN_HURT", "ACT_RESET", "ACT_SLAM_THROW_THROW_ND2", "ACT_MP_RUN_BUILDING", "ACT_FLINCH_STOMACH", "ACT_VM_SWINGHARD", "ACT_VM_PRIMARYATTACK_4", "ACT_MP_ATTACK_AIRWALK_GRENADE_PRIMARY", "ACT_DOD_PRIMARYATTACK_GREN_STICK", "ACT_DOD_HS_CROUCH_MP44", "ACT_VM_IIDLE", "ACT_HL2MP_JUMP_SHOTGUN", "ACT_VM_IIN_EMPTY", "Stack", "HITGROUP_RIGHTLEG", "ACT_VM_PRIMARYATTACK_SILENCED", "dragndrop", "ACT_HL2MP_IDLE_CROUCH_ZOMBIE_01", "ACT_SIGNAL_LEFT", "ACT_MP_GESTURE_VC_FINGERPOINT", "SavePresets", "PropSelect", "ACT_WALK_ON_FIRE", "ACT_VM_READY", "ENT_FILTER", "ACT_SLAM_DETONATOR_IDLE", "ACT_HOVER", "SNDLVL_NONE", "ACT_MP_RELOAD_STAND_LOOP", "ACT_VM_CRAWL_EMPTY", "ACT_DOD_PRONEWALK_IDLE_TOMMY", "cam", "ACT_VM_CRAWL", "ACT_HL2MP_GESTURE_RANGE_ATTACK_MAGIC", "ACT_SPECIAL_ATTACK2", "JS_Workshop", "ACT_DOD_PRONEWALK_IDLE_MP44", "COLLISION_GROUP_VEHICLE_CLIP", "ACT_HL2MP_GESTURE_RANGE_ATTACK_SCARED", "COND_WEAPON_HAS_LOS", "DNumSlider", "ACT_MP_JUMP_FLOAT", "ACT_HL2MP_RUN_SLAM", "KEY_END", "ConVarExists", "ACT_DOD_RUN_IDLE_GREASE", "ACT_IDLE_PACKAGE", "GetGlobalString", "DHTML", "ACT_SIGNAL_FORWARD", "ACT_DOD_WALK_AIM", "ACT_GESTURE_FLINCH_LEFTLEG", "ACT_DOD_CROUCHWALK_ZOOM_PSCHRECK", "ACT_GLOCK_SHOOTEMPTY", "ACT_DOD_WALK_IDLE_BAR", "ACT_DOD_SECONDARYATTACK_CROUCH_TOMMY", "SafeRemoveEntityDelayed", "ACT_HL2MP_IDLE_CROUCH_SHOTGUN", "ACT_GAUSS_SPINUP", "ACT_IDLE_AIM_STEALTH", "CRecipientFilter", "ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK", "ACT_HL2MP_JUMP_MELEE", "ACT_DOD_CROUCHWALK_AIM_MP40", "tostring", "ACT_DOD_PRIMARYATTACK_PRONE_GREN_STICK", "ACT_DOD_PRONE_ZOOM_BAZOOKA", "ACT_CLIMB_DISMOUNT", "SCHED_HIDE_AND_RELOAD", "TRACER_BEAM", "TYPE_TABLE", "ACT_DOD_PRIMARYATTACK_PRONE_MP40", "ACT_HL2MP_IDLE_PASSIVE", "ACT_HL2MP_IDLE_MELEE", "ACT_HL2MP_SWIM_CROSSBOW", "ACT_HL2MP_SWIM_IDLE_CROSSBOW", "ACT_DOD_PRONE_ZOOM_BOLT", "ACT_HL2MP_JUMP_CROSSBOW", "ACT_BUSY_LEAN_BACK_EXIT", "ACT_RANGE_ATTACK_ML", "player", "SF_CITIZEN_NOT_COMMANDABLE", "ACT_GESTURE_MELEE_ATTACK_SWING", "ACT_GESTURE_RANGE_ATTACK_SLAM", "ACT_MP_RELOAD_CROUCH_PRIMARY_LOOP", "DOFModeHack", "ACT_HL2MP_WALK_CROUCH_CROSSBOW", "WEAPON_PROFICIENCY_PERFECT", "ACT_VM_IDLE_8", "ACT_HL2MP_SWIM_ANGRY", "ACT_VM_PRIMARYATTACK_7", "ACT_GESTURE_SMALL_FLINCH", "ACT_RUN_SCARED", "ACT_VM_DEPLOY_7", "ACT_HL2MP_RUN_RPG", "ACT_WALK_AIM_STIMULATED", "ACT_HL2MP_IDLE_PHYSGUN", "LerpAngle", "ACT_MP_MELEE_GRENADE2_ATTACK", "ACT_MP_GRENADE2_IDLE", "isentity", "BroadcastLua", "ACT_DOD_CROUCHWALK_IDLE_MG", "ACT_HL2MP_WALK_CROUCH_DUEL", "ACT_HL2MP_IDLE_CROUCH_DUEL", "ACT_RUN_AIM_PISTOL", "ACT_CROSSBOW_FIDGET_UNLOADED", "ai_schedule", "CreateConVar", "ACT_WALK_AGITATED", "ACT_MP_RELOAD_AIRWALK_PRIMARY_END", "derma", "ACT_HL2MP_IDLE_DUEL", "SCHED_DUCK_DODGE", "ACT_IDLE_ANGRY", "ACT_VM_PRIMARYATTACK_6", "ACT_DOD_CROUCH_IDLE_BOLT", "ACT_DOD_STAND_AIM_MP44", "TYPE_PARTICLESYSTEM", "SURF_NODRAW", "ACT_VM_DEPLOYED_OUT", "KEY_PAD_1", "ACT_DIE_BACKSIDE", "ACT_DOD_RUN_AIM_KNIFE", "ACT_DOD_WALK_IDLE_RIFLE", "GWEN", "print", "ACT_HL2MP_IDLE_GRENADE", "ACT_DOD_SPRINT_IDLE_TOMMY", "EFL_FORCE_CHECK_TRANSMIT", "KEY_C", "ACT_RELOAD_START", "ACT_HL2MP_GESTURE_RELOAD_RPG", "CONTENTS_WINDOW", "language", "ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG", "ACT_HL2MP_GESTURE_RELOAD_SHOTGUN", "ACT_STEP_LEFT", "ACT_SIGNAL_TAKECOVER", "CLASS_METROPOLICE", "DMG_SHOCK", "baseclass", "ACT_HL2MP_WALK_CROUCH_MELEE", "ACT_MP_ATTACK_SWIM_PDA", "ACT_DOD_RUN_ZOOM_RIFLE", "ACT_MP_GESTURE_FLINCH_CHEST", "ACT_VM_PRIMARYATTACK_1", "ACT_VM_HITRIGHT", "ACT_DIEBACKWARD", "ACT_HL2MP_SWIM_IDLE_SHOTGUN", "ACT_VM_IDLE_6", "ConsoleAutoComplete", "ACT_MP_ATTACK_STAND_SECONDARYFIRE", "ACT_GESTURE_RANGE_ATTACK2_LOW", "ACT_RANGE_ATTACK_THROW", "ACT_DOD_CROUCHWALK_AIM_SPADE", "DMG_BLAST", "ACT_ROLL_LEFT", "ACT_HL2MP_RUN_SHOTGUN", "ACT_DOD_PRIMARYATTACK_BOLT", "ACT_HL2MP_WALK_SHOTGUN", "TimedSin", "ACT_HL2MP_GESTURE_RELOAD_AR2", "Button", "ACT_SLAM_THROW_THROW", "ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2", "ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED", "ACT_DOD_STAND_AIM_GREASE", "ACT_HL2MP_WALK_CROUCH_AR2", "ACT_HL2MP_IDLE_CROUCH_AR2", "ACT_WALK_AIM_RIFLE_STIMULATED", "ACT_GRENADE_ROLL", "ACT_BUSY_LEAN_LEFT_ENTRY", "ACT_HL2MP_SWIM_SMG1", "TYPE_INVALID", "TEXTUREFLAGS_UNUSED_00200000", "ACT_IDLE_SMG1", "RENDERGROUP_VIEWMODEL", "ACT_DOD_RELOAD_PRONE_GREASEGUN", "AddBackgroundImage", "SoundDuration", "ACT_MP_GESTURE_VC_THUMBSUP_MELEE", "ACT_VM_UNDEPLOY_2", "ACT_HL2MP_RUN_SMG1", "ACT_DOD_HS_CROUCH_MG42", "ACT_WALK_RIFLE", "ACT_HL2MP_IDLE_SMG1", "ACT_HL2MP_SWIM_PISTOL", "DModelSelect", "ACT_HL2MP_SWIM_IDLE_PISTOL", "ACT_HL2MP_JUMP_PISTOL", "ACT_RANGE_ATTACK_AR2_LOW", "ACT_DOD_PRONEWALK_AIM_GREN_FRAG", "ACT_DOD_PRONE_AIM_TOMMY", "ACT_VM_IDLE_1", "ACT_SLAM_THROW_DETONATOR_HOLSTER", "ACT_PLAYER_RUN_FIRE", "ACT_USE", "ACT_VM_UNDEPLOY_EMPTY", "ACT_MELEE_ATTACK_SWING", "ACT_HL2MP_RUN_PISTOL", "ACT_BARNACLE_CHEW", "ACT_MP_GESTURE_VC_NODYES_BUILDING", "ACT_HL2MP_GESTURE_RANGE_ATTACK", "ACT_DOD_RUN_AIM_MG", "ACT_HL2MP_IDLE", "chat", "ACT_GLIDE", "GetViewEntity", "ACT_WALK_CROUCH_AIM", "vgui", "ACT_SLAM_TRIPMINE_ATTACH2", "ACT_MP_GESTURE_FLINCH_STOMACH", "ACT_HL2MP_SWIM_IDLE_SUITCASE", "HTML", "ACT_RUN_STEALTH_PISTOL", "ACT_VM_PRIMARYATTACK_5", "RecipientFilter", "EFL_DIRTY_ABSVELOCITY", "IN_BACK", "CHAN_AUTO", "ACT_RUN_AIM_RELAXED", "PositionSpawnIcon", "ACT_DOD_RUN_IDLE_MP40", "ACT_MP_CROUCH_PRIMARY", "ACT_VM_RELOADEMPTY", "ACT_HL2MP_SWIM_ZOMBIE", "CT_UNIQUE", "ACT_MP_ATTACK_CROUCH_PREFIRE", "GetHostName", "ACT_HL2MP_SIT_FIST", "ACT_DOD_STAND_IDLE_MP44", "ACT_WALK_AIM_AGITATED", "AvatarImage", "COLLISION_GROUP_WEAPON", "DProgress", "KEY_F12", "ACT_RANGE_ATTACK_SLAM", "ACT_DOD_PRONEWALK_AIM_GREN_STICK", "ACT_VM_DRAW_M203", "ACT_HL2MP_GESTURE_RELOAD_SCARED", "ACT_HL2MP_IDLE_SUITCASE", "BLOOD_COLOR_GREEN", "ACT_HL2MP_WALK_SCARED", "undo", "ACT_VM_PRIMARYATTACK_DEPLOYED_8", "KEY_NONE", "ACT_HL2MP_JUMP_ANGRY", "IGModAudioChannel", "ACT_HL2MP_GESTURE_RELOAD_ANGRY", "hammer", "Label", "IsMounted", "ACT_MP_GESTURE_FLINCH_SECONDARY", "ENT_POINT", "ACT_HL2MP_SWIM_GRENADE", "ACT_WALK_AIM_STEALTH", "KEY_2", "pcall", "ACT_HL2MP_IDLE_CROUCH_CAMERA", "SND_IGNORE_NAME", "isbool", "ACT_DI_ALYX_HEADCRAB_MELEE", "ACT_MP_SWIM_IDLE", "AddonMaterial", "ACT_DOD_RUN_AIM_GREASE", "ACT_HL2MP_JUMP_REVOLVER", "ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER", "Derma_DrawBackgroundBlur", "ACT_VICTORY_DANCE", "markup", "NAV_MESH_JUMP", "ACT_HL2MP_WALK_REVOLVER", "CT_DOWNTRODDEN", "getfenv", "PrecacheSentenceGroup", "MAT_GLASS", "file", "ACT_WALK_PACKAGE", "ConVar", "DisableClipping", "ACT_GESTURE_TURN_RIGHT45", "ACT_HL2MP_JUMP_MAGIC", "ACT_FLINCH_PHYSICS", "ACT_BARNACLE_CHOMP", "Derma_Install_Convar_Functions", "AddOriginToPVS", "ACT_RANGE_ATTACK_PISTOL", "ACT_HL2MP_WALK_CROUCH_MAGIC", "ACT_HL2MP_RUN_MAGIC", "ACT_HL2MP_IDLE_MAGIC", "ACT_HL2MP_SIT_SLAM", "ACT_ZOMBIE_CLIMB_START", "ACT_DOD_PRONEWALK_IDLE_C96", "ACT_BUSY_SIT_CHAIR_ENTRY", "ACT_DOD_ZOOMLOAD_BAZOOKA", "ACT_DOD_RUN_IDLE_BAZOOKA", "ACT_RUN_CROUCH_AIM_RIFLE", "ACT_OBJ_ASSEMBLING", "ACT_GMOD_GESTURE_RANGE_FRENZY", "TEXTUREFLAGS_BORDER", "ACT_MP_PRIMARY_GRENADE2_ATTACK", "ACT_HL2MP_WALK_CROUCH_ZOMBIE_05", "widgets", "ACT_DOD_PRONE_AIM_GREASE", "Player", "ACT_HL2MP_WALK_CROUCH_ZOMBIE_04", "ACT_HL2MP_WALK_CROUCH_ZOMBIE_02", "SNDLVL_IDLE", "File", "ACT_RUN_AIM_STIMULATED", "ACT_HL2MP_WALK_ZOMBIE_03", "ACT_HL2MP_WALK_ZOMBIE_02", "ACT_HL2MP_RUN_CHARGING", "DColorCube", "LAST_VISIBLE_CONTENTS", "ACT_DOD_STAND_IDLE_TOMMY", "ACT_GMOD_TAUNT_CHEER", "ACT_DOD_PRIMARYATTACK_PRONE_PSCHRECK", "ACT_OVERLAY_SHIELD_UP_IDLE", "ACT_GESTURE_RANGE_ATTACK1_LOW", "ACT_CROUCHING_SHIELD_UP_IDLE", "ACT_VM_IOUT_EMPTY", "ACT_GMOD_GESTURE_WAVE", "ACT_GMOD_TAUNT_SALUTE", "CAP_MOVE_GROUND", "ACT_RUN_RPG", "NamedColor", "ACT_GMOD_GESTURE_BECON", "MOVETYPE_CUSTOM", "ACT_DOD_RELOAD_PRONE_BAZOOKA", "COND_SEE_FEAR", "ACT_GESTURE_FLINCH_BLAST", "ScrW", "ACT_RANGE_ATTACK_TRIPWIRE", "ACT_MP_GESTURE_VC_HANDMOUTH_PDA", "ACT_GESTURE_FLINCH_LEFTARM", "CAP_USE", "ACT_TRIPMINE_GROUND", "ACT_IDLE_STIMULATED", "ACT_IDLE", "ACT_VM_IDLE_DEPLOYED_6", "SCHED_ALERT_STAND", "ACT_RUN_CROUCH_RIFLE", "IN_ZOOM", "FL_ONGROUND", "ACT_MP_GESTURE_VC_HANDMOUTH_MELEE", "ACT_VM_RELOAD_END", "ACT_DOD_HS_IDLE_PISTOL", "ACT_MP_GESTURE_VC_FISTPUMP_SECONDARY", "MAT_DIRT", "SIM_LOCAL_FORCE", "ACT_GESTURE_RANGE_ATTACK2", "ACT_MP_GESTURE_VC_NODYES_PRIMARY", "ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY", "resource", "ACT_MP_ATTACK_STAND_PDA", "Material", "ACT_MP_JUMP_FLOAT_PDA", "ACT_MP_JUMP_START_PDA", "ACT_SLAM_STICKWALL_DETONATOR_HOLSTER", "ACT_GESTURE_FLINCH_BLAST_DAMAGED", "DIRECTIONAL_USE", "HITGROUP_STOMACH", "CMoveData", "DTileLayout", "ACT_MP_CROUCH_PDA", "ACT_ZOMBIE_CLIMB_END", "KEY_Q", "ACT_MP_ATTACK_CROUCH_GRENADE_BUILDING", "GetDemoFileDetails", "ACT_DIE_FRONTSIDE", "ACT_MP_SWIM_PRIMARY", "ACT_MP_ATTACK_CROUCH_BUILDING", "DEFINE_BASECLASS", "ACT_SLAM_STICKWALL_DETONATE", "ACT_MP_ATTACK_STAND_BUILDING", "DHorizontalDivider", "ACT_HL2MP_GESTURE_RELOAD_MAGIC", "ACT_WALK_CROUCH_AIM_RIFLE", "Awesomium", "ACT_MP_CROUCHWALK_BUILDING", "ACT_MP_AIRWALK_BUILDING", "ACT_MP_WALK_BUILDING", "ACT_DOD_PRONEWALK_IDLE_GREASE", "MATERIAL_LINE_LOOP", "KEY_F2", "ACT_MP_SECONDARY_GRENADE2_ATTACK", "ACT_MP_ATTACK_CROUCH_POSTFIRE", "ACT_MP_GESTURE_VC_FINGERPOINT_SECONDARY", "DPropertySheet", "ACT_DOD_SPRINT_IDLE_30CAL", "GameDetails", "USE_OFF", "MAT_PLASTIC", "ACT_VM_IDLE_DEPLOYED_8", "ACT_MP_GRENADE2_ATTACK", "ACT_MP_GRENADE1_ATTACK", "ACT_DOD_CROUCHWALK_AIM_TOMMY", "ACT_MP_RELOAD_STAND_SECONDARY_END", "ACT_VM_FIZZLE", "GetGlobalFloat", "ACT_SPRINT", "ACT_WALK", "SCREENFADE", "tobool", "ACT_DOD_PRONE_AIM_SPADE", "ACT_DOD_RUN_AIM_GREN_FRAG", "KEY_PAD_2", "DMG_CLUB", "ACT_WALK_AIM_STEALTH_PISTOL", "GO_WEST", "IN_GRENADE2", "SOLID_OBB", "EF_PARENT_ANIMATES", "ACT_CROUCHIDLE", "BONE_PHYSICALLY_SIMULATED", "ACT_HL2MP_SIT_CROSSBOW", "ACT_MP_CROUCH_MELEE", "DFileBrowser", "SOLID_BBOX", "CHAN_WEAPON", "ErrorNoHalt", "ErrorNoHaltWithStack", "ACT_HL2MP_RUN_DUEL", "ACT_DOD_WALK_ZOOM_BAZOOKA", "type", "MOVETYPE_STEP", "ACT_MP_GESTURE_FLINCH_HEAD", "ACT_SLAM_THROW_TO_TRIPMINE_ND", "ACT_DOD_HS_IDLE_MP44", "ACT_DOD_STAND_IDLE_PISTOL", "ACT_GESTURE_RANGE_ATTACK_AR1", "ACT_DOD_RUN_AIM_PISTOL", "ACT_DIE_HEADSHOT", "ACT_MP_JUMP_FLOAT_SECONDARY", "ACT_FLINCH_LEFTARM", "NumModelSkins", "ACT_MP_RELOAD_SWIM_PRIMARY_END", "DrawSharpen", "ACT_VM_USABLE_TO_UNUSABLE", "ACT_DOD_WALK_IDLE_BAZOOKA", "ACT_VM_PRIMARYATTACK_DEPLOYED_EMPTY", "ACT_MP_ATTACK_AIRWALK_PRIMARY", "ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED", "ACT_RUN_CROUCH_RPG", "TYPE_NAVLADDER", "DebugInfo", "ACT_HL2MP_RUN_MELEE2", "ACT_SLAM_THROW_THROW2", "BOX_LEFT", "ACT_DOD_CROUCHWALK_AIM_30CAL", "ACT_MP_RELOAD_SWIM", "ACT_GESTURE_BARNACLE_STRANGLE", "ACT_MP_RELOAD_CROUCH_LOOP", "CONTENTS_CURRENT_DOWN", "DProperty_Boolean", "ACT_MP_ATTACK_SWIM_GRENADE", "math", "ImageCheckBox", "ACT_MP_ATTACK_CROUCH_PRIMARYFIRE", "ACT_DOD_PRIMARYATTACK_PRONE", "ACT_MP_ATTACK_STAND_PRIMARYFIRE", "ACT_SHOTGUN_IDLE_DEEP", "CAP_OPEN_DOORS", "RecordDemoFrame", "MAT_COMPUTER", "Vehicle", "ACT_HL2MP_IDLE_SLAM", "ACT_MP_CROUCH_DEPLOYED_IDLE", "FrameNumber", "ACT_DOD_PLANT_TNT", "ACT_DOD_HS_CROUCH_TOMMY", "CLASS_NONE", "ACT_DOD_HS_IDLE_30CAL", "ACT_MP_GESTURE_FLINCH_LEFTARM", "ACT_DOD_PRIMARYATTACK_PRONE_C96", "ACT_DOD_SECONDARYATTACK_CROUCH", "ACT_DOD_PRONE_ZOOM_FORWARD_PSCHRECK", "ACT_DOD_CROUCH_IDLE_PISTOL", "ACT_GESTURE_RANGE_ATTACK_THROW", "ACT_DEPLOY_IDLE", "ACT_DOD_RELOAD_PRONE_BAR", "ACT_DOD_RELOAD_PRONE_MP40", "ACT_COWER", "GESTURE_SLOT_JUMP", "ACT_DOD_RELOAD_DEPLOYED_MG34", "ACT_DOD_RELOAD_DEPLOYED_MG", "ACT_IDLE_RPG_RELAXED", "AccessorFunc", "ACT_DOD_RELOAD_CROUCH_PISTOL", "GetConVarString", "ScreenScale", "ACT_DOD_RELOAD_CROUCH_MP40", "ACT_SLAM_THROW_DETONATE", "ACT_GESTURE_FLINCH_BLAST_SHOTGUN", "ACT_DOD_WALK_IDLE_MP40", "CtrlListBox", "SIM_GLOBAL_ACCELERATION", "ACT_HL2MP_WALK_CROUCH_KNIFE", "ACT_DOD_RUN_AIM_TOMMY", "SANDBOX", "ACT_PHYSCANNON_ANIMATE", "ACT_SLAM_DETONATOR_STICKWALL_DRAW", "ACT_SLAM_STICKWALL_ATTACH", "ACT_GESTURE_TURN_LEFT", "ACT_VM_IDLE_DEPLOYED_1", "ACT_BUSY_LEAN_LEFT", "EFL_NO_ROTORWASH_PUSH", "ACT_DOD_SECONDARYATTACK_RIFLE", "IMAGE_FORMAT_ARGB8888", "DMG_POISON", "SortedPairsByMemberValue", "ACT_DOD_WALK_ZOOM_BOLT", "SF_ROLLERMINE_FRIENDLY", "ACT_MP_JUMP_FLOAT_BUILDING", "LoadLastMap", "ACT_HL2MP_SWIM_MELEE", "CLASS_PLAYER_ALLY_VITAL", "ACT_DOD_PRONE_AIM_PISTOL", "ACT_DOD_WALK_AIM_KNIFE", "ACT_VM_DEPLOY_8", "ACT_GMOD_GESTURE_ITEM_THROW", "SpawnIcon", "ACT_DOD_CROUCH_IDLE_MG", "gmsave", "CSEnt", "SOLID_OBB_YAW", "ACT_GESTURE_TURN_RIGHT90_FLAT", "bf_read", "ACT_DOD_STAND_AIM_BOLT", "ACT_DOD_STAND_AIM_RIFLE", "ACT_DOD_RUN_IDLE_C96", "ACT_SLAM_STICKWALL_TO_THROW", "ACT_HL2MP_JUMP_CAMERA", "ACT_SLAM_STICKWALL_IDLE", "ACT_WALK_AIM_PISTOL", "ACT_MP_GESTURE_VC_THUMBSUP_SECONDARY", "FL_DISSOLVING", "ACT_RANGE_ATTACK_RPG", "ACT_DOD_CROUCH_AIM_30CAL", "ACT_VM_UNDEPLOY_4", "ACT_DOD_CROUCHWALK_IDLE_RIFLE", "ACT_VM_IDLE_DEPLOYED_5", "KEY_SCROLLLOCKTOGGLE", "isstring", "ACT_IDLE_SHOTGUN_STIMULATED", "VMatrix", "ACT_DOD_RELOAD_PRONE_GARAND", "controlpanel", "ACT_MP_RELOAD_AIRWALK_PRIMARY", "DVerticalDivider", "ACT_VM_RELOAD_SILENCED", "SScale", "CLASS_HACKED_ROLLERMINE", "ACT_VM_DOWN_M203", "DListBox", "ACT_RUN_RIFLE_RELAXED", "ACT_OBJ_RUNNING", "ACT_WALK_STEALTH", "IsFirstTimePredicted", "ACT_VM_PRIMARYATTACK_DEPLOYED_6", "ACT_MP_JUMP_LAND_BUILDING", "ACT_PHYSCANNON_DETACH", "DButton", "ACT_VM_HITLEFT", "ACT_JUMP", "SCHED_GET_HEALTHKIT", "ACT_RELOAD" -- End generated code } ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2015 Brian Hang, Kyu Yeon Lee Copyright (c) 2018-2021 Alexander Grist-Hucker, Igor Radovanovic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Helix

Discord Build Status

Helix is a framework for roleplay gamemodes in [Garry's Mod](https://gmod.facepunch.com/), based off of [NutScript 1.1](https://github.com/rebel1324/NutScript). Helix provides a stable, feature-filled, open-source, and DRM-free base so you can focus more on the things you want: making gameplay. ## Getting Started Visit the getting started guide in the [documentation](https://docs.gethelix.co/manual/getting-started/) for an in-depth guide. If you know what you're doing, a quick start for bootstrapping your own schema is forking/copying the skeleton schema at https://github.com/nebulouscloud/helix-skeleton. The skeleton contains all the important elements you need to have a functioning schema so you can get to coding right away. You can also use our HL2 RP schema at https://github.com/nebulouscloud/helix-hl2rp as a base to work off of if you need something more fleshed out. ## Plugins If you'd like to enhance your gamemode, you can use any of the freely provided plugins available at the [Helix Plugin Center](https://plugins.gethelix.co). It is also encouraged to submit your own plugins for others to find and use at https://github.com/nebulouscloud/helix-plugins ## Documentation Up-to-date documentation can be found at https://docs.gethelix.co. This is automatically updated when commits are pushed to the master branch. If you'd like to ask some questions or integrate with the community, you can always join our [Discord](https://discord.gg/2AutUcF) server. We highly encourage you to search through the documentation before posting a question - the docs contain a good deal of information about how the various systems in Helix work, and it might explain what you're looking for. ### Building documentation If you're planning on contributing to the documentation, you'll probably want to preview your changes before you commit. The documentation can be built using [LDoc](https://github.com/impulsh/ldoc) - note that we use a forked version to add some functionality. You'll need [LuaRocks](https://luarocks.org/) installed in order to get started. ```shell # installing ldoc git clone https://github.com/impulsh/ldoc cd ldoc luarocks make # navigate to the helix repo folder and run ldoc . ``` You may not see the syntax highlighting work on your local copy - you'll need to copy the files in `docs/js` and `docs/css` over into the `docs/html` folder after it's done building. ## Contributing Feel free to submit a pull request with any fixes/changes that you might find beneficial. Currently, there are no solid contributing guidelines other than keeping your code consistent with the rest of the framework. ## Acknowledgements Helix is a fork of NutScript 1.1 by [Chessnut](https://github.com/brianhang) and [rebel1324](https://github.com/rebel1324). ================================================ FILE: config.ld ================================================ file = { "gamemode", "plugins", "docs/hooks", exclude = {"gamemode/core/libs/thirdparty"} } module_file = { Character = "gamemode/core/meta/sh_character.lua", Entity = "gamemode/core/meta/sh_entity.lua", Inventory = "gamemode/core/meta/sh_inventory.lua", Item = "gamemode/core/meta/sh_item.lua", Player = "gamemode/core/meta/sh_player.lua" } dir = "docs/html" project = "Helix" title = "Helix Documentation" no_space_before_args = true style = "docs/css" template = "docs/templates" format = "markdown" ignore = true topics = "docs/manual" use_markdown_titles = true kind_names = {module = "Libraries", topic = "Manual"} merge = true sort = true sort_modules = true simple_args_string = true -- we show optionals/defaults outside of the display name strip_metamethod_prefix = true -- remove the name of the table when displaying metamethod names no_viewed_topic_at_top = true -- don't put the currently viewed topic at the top use_new_templates = true -- new templating system pretty_urls = true -- avoid showing .html in urls pretty_topic_names = true -- strips extension from manual filenames, this does not check filename collisions custom_tags = { {"realm", hidden = true}, {"internal", hidden = true} } custom_display_name_handler = function(item, default_handler) if (item.type == "function" and item.module) then if (item.module.type == "classmod" or item.module.type == "panel") then return item.module.mod_name .. ":" .. default_handler(item) elseif (item.module.type == "hooks") then return item.module.mod_name:upper() .. ":" .. default_handler(item) end end return default_handler(item) end new_type("hooks", "Hooks", true) new_type("panel", "Panels", true) -- helix types tparam_alias("char", "Character") tparam_alias("inventory", "Inventory") tparam_alias("item", "Item") tparam_alias("ixtype", "ix.type") tparam_alias("date", "date") -- standard types tparam_alias("string", "string") tparam_alias("bool", "boolean") tparam_alias("func", "function") tparam_alias("player", "Player") tparam_alias("entity", "Entity") tparam_alias("color", "color") tparam_alias("tab", "table") tparam_alias("material", "material") tparam_alias("vector", "vector") tparam_alias("angle", "angle") ================================================ FILE: docs/css/highlight.css ================================================ /* github.com style (c) Vasily Polovnyov */ .hljs { display: block; color: #333; } .hljs-comment, .hljs-quote { color: #535346; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst { color: #333; font-weight: bold; } .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: #008080; } .hljs-string, .hljs-doctag { color: #d14; } .hljs-title, .hljs-section, .hljs-selector-id { color: #900; font-weight: bold; } .hljs-subst { font-weight: normal; } .hljs-type, .hljs-class .hljs-title { color: #458; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { color: #000080; font-weight: normal; } .hljs-regexp, .hljs-link { color: #009926; } .hljs-symbol, .hljs-bullet { color: #990073; } .hljs-built_in, .hljs-builtin-name { color: #0086b3; } .hljs-meta { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: docs/css/ldoc.css ================================================ :root { --content-width: 1200px; --sidebar-width: 330px; --padding-big: 48px; --padding-normal: 24px; --padding-small: 16px; --padding-tiny: 10px; --font-massive: 32px; --font-huge: 24px; --font-big: 18px; --font-normal: 16px; --font-tiny: 12px; --font-style-normal: Segoe UI, Helvetica, Arial, sans-serif; --font-style-code: Consolas, monospace; --color-accent: rgb(115, 53, 142); --color-accent-dark: rgb(85, 39, 105); --color-white: rgb(255, 255, 255); --color-offwhite: rgb(200, 200, 200); --color-white-accent: rgb(203, 190, 209); --color-black: rgb(0, 0, 0); --color-lightgrey: rgb(160, 160, 160); --color-background-light: rgb(240, 240, 240); --color-background-dark: rgb(33, 33, 33); } * { padding: 0; margin: 0; box-sizing: border-box; } body { background-color: var(--color-background-light); font-family: var(--font-style-normal); display: flex; flex-direction: column; } a { color: inherit; text-decoration: inherit; } h1, h2, h3, h4 { font-weight: 400; } ul li { margin-left: var(--padding-small); } /* landing */ .landing { background-color: var(--color-accent); color: var(--color-white); padding: 128px 0 128px 0; } .landing h1 { margin: 0; padding: 0; border: none; font-weight: 100; font-size: var(--font-massive); text-align: center; } .wrapper { padding: var(--padding-small); } details { user-select: none; } details summary { outline: none; } code { font-family: "Source Code Pro", monospace; font-size: 85%; white-space: pre; tab-size: 4; -moz-tab-size: 4; padding: 2px 4px; background-color: rgb(33, 33, 33, 0.1); } pre { background-color: rgb(33, 33, 33, 0.1); margin-top: var(--padding-small); padding: var(--padding-tiny); overflow: auto; } pre code { background-color: transparent; } span.realm { width: 14px; height: 14px; border-radius: 3px; display: inline-block; margin-right: 6px; } span.realm.shared { background: linear-gradient(45deg, #f80 0%, #f80 50%, #08f 51%, #08f 100%); } span.realm.client { background-color: #f80; } span.realm.server { background-color: #08f; } /* wrapper element for sidebar/content */ main { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; width: var(--content-width); margin: auto; } /* sidebar */ nav { color: var(--color-offwhite); background-color: var(--color-background-dark); position: fixed; display: flex; flex-direction: column; width: var(--sidebar-width); height: 100%; } /* sidebar header */ nav header { color: var(--color-white); background-color: var(--color-accent); padding: var(--padding-small); } nav header h1 { font-size: var(--font-huge); font-weight: 100; text-align: center; margin-bottom: var(--padding-small); } #search { background-color: var(--color-accent-dark); color: var(--color-white); border: none; font-size: var(--font-normal); outline: none; width: 100%; padding: 6px; } #search::placeholder { color: var(--color-white-accent); } #search::-webkit-search-cancel-button { display: none; } /* sidebar contents */ nav section { padding: var(--padding-small); overflow: auto; } nav section ul { list-style-type: none; } nav section::-webkit-scrollbar, pre::-webkit-scrollbar { width: 8px; height: 8px; } nav section::-webkit-scrollbar-track, pre::-webkit-scrollbar-track { background: transparent; } nav section::-webkit-scrollbar-thumb { background-color: var(--color-lightgrey); } pre::-webkit-scrollbar-thumb { background-color: var(--color-lightgrey); } /* sidebar contents category */ nav section details.category { padding-top: var(--padding-tiny); } nav section details.category > ul > li { margin: 0; line-height: 1.5; } nav section details.category > ul > li a { display: inline-block; width: 90%; } nav section details.category:first-of-type { padding-top: calc(var(--padding-tiny) * -1); } nav section details.category summary::-webkit-details-marker { opacity: 0.5; cursor: pointer; } nav section details.category summary h2 { color: var(--color-accent); font-size: var(--font-big); letter-spacing: 2px; text-transform: uppercase; cursor: pointer; padding-bottom: var(--padding-tiny); } /* content */ article { background-color: rgb(255, 255, 255); width: calc(100% - var(--sidebar-width)); min-height: 100vh; margin-left: var(--sidebar-width); } article .wrapper > *:first-child { margin-top: 0; } /* header */ article header { color: rgb(255, 255, 255); background-color: rgb(115, 53, 142); padding: var(--padding-tiny); } article header h1 { border-bottom: 1px solid rgba(255, 255, 255, 0.25); padding-bottom: 8px; font-family: var(--font-style-code); margin: 0; } article header h2 { padding-top: var(--padding-tiny); margin: 0; font-size: var(--font-normal); font-weight: normal; } article header.module a { color: white !important; text-decoration: underline; } details.category > summary { list-style: none; } details.category > summary::-webkit-details-marker { display: none; } article h1 { font-size: 28px; font-weight: 600; border-bottom: 1px solid rgba(0, 0, 0, 0.25); margin-top: 24px; margin-bottom: 16px; padding-bottom: 8px; } article h2 { font-size: 20px; font-weight: 600; margin-top: 12px; } article h3 { color: rgb(115, 53, 142); margin-top: var(--padding-tiny); text-transform: uppercase; } article p { margin-top: var(--padding-small); } article p a, article ul li a, article h1 a, article h2 a { color: rgb(115, 53, 142); font-weight: 600; } article h1.title { color: rgb(255, 255, 255); background-color: rgb(115, 53, 142); margin-top: var(--padding-small); margin-bottom: 0; padding: var(--padding-tiny); font-size: var(--font-big); font-weight: 100; letter-spacing: 2px; text-transform: uppercase; } a.reference { color: rgb(115, 53, 142); float: right; margin-top: 8px; padding-left: 8px; font-size: 14px; font-weight: 600; } .notice { --color-notice-background: var(--color-accent); --color-notice-text: var(--color-notice-background); margin-top: var(--padding-tiny); border: 2px solid var(--color-notice-background); } .notice.error { --color-notice-background: rgb(194, 52, 130); } .notice.warning { --color-notice-background: rgb(224, 169, 112); --color-notice-text: rgb(167, 104, 37); } .notice .title { color: var(--color-white); background-color: var(--color-notice-background); padding: var(--padding-tiny); font-size: var(--font-normal); text-transform: uppercase; letter-spacing: 2px; } .notice p { color: var(--color-notice-text); margin: 0 !important; padding: var(--padding-tiny); } /* function/table */ .method { display: flex; flex-flow: column; background-color: rgb(230, 230, 230); padding: var(--padding-tiny); margin-top: var(--padding-small); } .method header { color: rgb(0, 0, 0); background-color: inherit; padding: 0; order: -1; } .method header .anchor { color: inherit; text-decoration: inherit; } .method header .anchor:target h1 { background-color: rgba(115, 53, 142, 0.2); background-clip: content-box; } .method header h1 { font-family: "Source Code Pro", monospace; padding-bottom: var(--padding-tiny); border-bottom: 1px solid rgba(0, 0, 0, 0.25); font-size: 20px; } .method header p:first-of-type { margin-top: var(--padding-tiny); } .method h3 { color: rgb(115, 53, 142); font-size: var(--font-normal); letter-spacing: 2px; text-transform: uppercase; } .method pre { margin-top: var(--padding-tiny); } @media only screen and (max-width: 1100px) { main nav { position: inherit; } main article { margin-left: 0; } } .method ul { margin-top: var(--padding-tiny); background-color: inherit; } .method ul li { list-style: none; margin: 4px 0 0 var(--padding-normal); } .method ul li:first-of-type { margin-top: 0; } .method ul li p { margin: 4px 0 0 var(--padding-normal); } .method ul li pre { margin: 4px 0 0 var(--padding-normal); } .method ul li a { color: rgb(115, 53, 142); font-weight: 600; } /* we have to manually specify these instead of making a shared class since you cannot customize the parameter class in ldoc */ .parameter, .type, .default { display: inline-block; color: rgb(255, 255, 255) !important; padding: 4px; font-size: 14px; font-family: "Source Code Pro", monospace; } .parameter { background-color: rgb(115, 53, 142); } .type { background-color: rgb(31, 141, 155); } a.type { font-weight: 300 !important; text-decoration: underline; } .default { background-color: rgb(193, 114, 11); } .type a { padding: 0; } .or { color: rgba(115, 53, 142, 0.5); background-color: inherit; width: calc(100% - 32px); height: 8px; margin: 0 0 8px 32px; text-align: center; font-weight: 600; border-bottom: 1px solid rgba(115, 53, 142, 0.5); } .or span { background-color: inherit; padding: 0 8px 0 8px; } ================================================ FILE: docs/hooks/class.lua ================================================ -- luacheck: ignore 111 --[[-- Class setup hooks. As with `Faction`s, `Class`es get their own hooks for when players leave/join a class, etc. These hooks are only valid in class tables that are created in `schema/classes/sh_classname.lua`, and cannot be used like regular gamemode hooks. ]] -- @hooks Class --- Whether or not a player can switch to this class. -- @realm shared -- @player client Client that wants to switch to this class -- @treturn bool True if the player is allowed to switch to this class -- @usage function CLASS:CanSwitchTo(client) -- return client:IsAdmin() -- only admins allowed in this class! -- end function CanSwitchTo(client) end --- Called when a character has left this class and has joined a different one. You can get the class the character has -- has joined by calling `character:GetClass()`. -- @realm server -- @player client Player who left this class function OnLeave(client) end --- Called when a character has joined this class. -- @realm server -- @player client Player who has joined this class -- @usage function CLASS:OnSet(client) -- client:SetModel("models/police.mdl") -- end function OnSet(client) end --- Called when a character in this class has spawned in the world. -- @realm server -- @player client Player that has just spawned function OnSpawn(client) end ================================================ FILE: docs/hooks/faction.lua ================================================ -- luacheck: ignore 111 --[[-- Faction setup hooks. Factions get their own hooks that are called for various reasons, but the most common one is to set up a character once it's created and assigned to a certain faction. For example, giving a police faction character a weapon on creation. These hooks are used in faction tables that are created in `schema/factions/sh_factionname.lua` and cannot be used like regular gamemode hooks. ]] -- @hooks Faction --- Called when the default name for a character needs to be retrieved (i.e upon initial creation). -- @realm shared -- @player client Client to get the default name for -- @treturn string Default name for the newly created character -- @usage function FACTION:GetDefaultName(client) -- return "MPF-RCT." .. tostring(math.random(1, 99999)) -- end function GetDefaultName(client) end --- Called when a character has been initally created and assigned to this faction. -- @realm server -- @player client Client that owns the character -- @char character Character that has been created -- @usage function FACTION:OnCharacterCreated(client, character) -- local inventory = character:GetInventory() -- inventory:Add("pistol") -- end function OnCharacterCreated(client, character) end --- Called when a character in this faction has spawned in the world. -- @realm server -- @player client Player that has just spawned function OnSpawn(client) end --- Called when a player's character has been transferred to this faction. -- @realm server -- @char character Character that was transferred -- @usage function FACTION:OnTransferred(character) -- character:SetModel(self.models[1]) -- end function OnTransferred(character) end ================================================ FILE: docs/hooks/plugin.lua ================================================ -- luacheck: ignore 111 --[[-- Global hooks for general use. Plugin hooks are regular hooks that can be used in your schema with `Schema:HookName(args)`, in your plugin with `PLUGIN:HookName(args)`, or in your addon with `hook.Add("HookName", function(args) end)`. ]] -- @hooks Plugin --- Adjusts the data used just before creating a new character. -- @realm server -- @player client Player that is creating the character -- @tab payload Table of data to be used for character creation -- @tab newPayload Table of data be merged with the current payload -- @usage function PLUGIN:AdjustCreationPayload(client, payload, newPayload) -- newPayload.money = payload.attributes["stm"] -- Sets the characters initial money to the stamina attribute value. -- end function AdjustCreationPayload(client, payload, newPayload) end --- Adjusts a player's current stamina offset amount. This is called when the player's stamina is about to be changed; every -- `0.25` seconds on the server, and every frame on the client. -- @realm shared -- @player client Player whose stamina is changing -- @number baseOffset Amount the stamina is changing by. This can be a positive or negative number depending if they are -- exhausting or regaining stamina -- @treturn number New offset to use -- @usage function PLUGIN:AdjustStaminaOffset(client, baseOffset) -- return baseOffset * 2 -- Drain/Regain stamina twice as fast. -- end function AdjustStaminaOffset(client, baseOffset) end --- Creates the business panel in the tab menu. -- @realm client -- @treturn bool Whether or not to create the business menu -- @usage function PLUGIN:BuildBusinessMenu() -- return LocalPlayer():IsAdmin() -- Only builds the business menu for admins. -- end function BuildBusinessMenu() end --- Whether or not a message can be auto formatted with punctuation and capitalization. -- @realm server -- @player speaker Player that sent the message -- @string chatType Chat type of the message. This will be something registered with `ix.chat.Register` - like `ic`, `ooc`, etc. -- @string text Unformatted text of the message -- @treturn bool Whether or not to allow auto formatting on the message -- @usage function PLUGIN:CanAutoFormatMessage(speaker, chatType, text) -- return false -- Disable auto formatting outright. -- end function CanAutoFormatMessage(speaker, chatType, text) end --- Whether or not certain information can be displayed in the character info panel in the tab menu. -- @realm client -- @tab suppress Information to **NOT** display in the UI - modify this to change the behaviour. This is a table of the names of -- some panels to avoid displaying. Valid names include: -- -- - `time` - current in-game time -- - `name` - name of the character -- - `description` - description of the character -- - `characterInfo` - entire panel showing a list of additional character info -- - `faction` - faction name of the character -- - `class` - name of the character's class if they're in one -- - `money` - current money the character has -- - `attributes` - attributes list for the character -- -- Note that schemas/plugins can add additional character info panels. -- @usage function PLUGIN:CanCreateCharacterInfo(suppress) -- suppress.attributes = true -- Hides the attributes panel from the character info tab -- end function CanCreateCharacterInfo(suppress) end --- Whether or not the ammo HUD should be drawn. -- @realm client -- @entity weapon Weapon the player currently is holding -- @treturn bool Whether or not to draw the ammo hud -- @usage function PLUGIN:CanDrawAmmoHUD(weapon) -- if (weapon:GetClass() == "weapon_frag") then -- Hides the ammo hud when holding grenades. -- return false -- end -- end function CanDrawAmmoHUD(weapon) end --- Called when a player tries to use abilities on the door, such as locking. -- @realm shared -- @player client The client trying something on the door. -- @entity door The door entity itself. -- @number access The access level used when called. -- @treturn bool Whether or not to allow the client access. -- @usage function PLUGIN:CanPlayerAccessDoor(client, door, access) -- return true -- Always allow access. -- end function CanPlayerAccessDoor(client, door, access) end --- Whether or not a player is allowed to combine an item `other` into the given `item`. -- @realm server -- @player client Player attempting to combine an item into another -- @number item instance ID of the item being dropped onto -- @number other instance ID of the item being combined into the first item, this can be invalid due to it being from clientside -- @treturn bool Whether or not to allow the player to combine the items -- @usage function PLUGIN:CanPlayerCombineItem(client, item, other) -- local otherItem = ix.item.instances[other] -- -- if (otherItem and otherItem.uniqueID == "soda") then -- return false -- disallow combining any item that has a uniqueID equal to `soda` -- end -- end function CanPlayerCombineItem(client, item, other) end --- Whether or not a player is allowed to create a new character with the given payload. -- @realm server -- @player client Player attempting to create a new character -- @tab payload Data that is going to be used for creating the character -- @treturn bool Whether or not the player is allowed to create the character. This function defaults to `true`, so you -- should only ever return `false` if you're disallowing creation. Otherwise, don't return anything as you'll prevent any other -- calls to this hook from running. -- @treturn string Language phrase to use for the error message -- @treturn ... Arguments to use for the language phrase -- @usage function PLUGIN:CanPlayerCreateCharacter(client, payload) -- if (!client:IsAdmin()) then -- return false, "notNow" -- only allow admins to create a character -- end -- end -- -- non-admins will see the message "You are not allowed to do this right now!" function CanPlayerCreateCharacter(client, payload) end --- Whether or not a player is allowed to drop the given `item`. -- @realm server -- @player client Player attempting to drop an item -- @number item instance ID of the item being dropped -- @treturn bool Whether or not to allow the player to drop the item -- @usage function PLUGIN:CanPlayerDropItem(client, item) -- return false -- Never allow dropping items. -- end function CanPlayerDropItem(client, item) end --- Whether or not a player can earn money at regular intervals. This hook runs only if the player's character faction has -- a salary set - i.e `FACTION.pay` is set to something other than `0` for their faction. -- @realm server -- @player client Player to give money to -- @tab faction Faction of the player's character -- @treturn bool Whether or not to allow the player to earn salary -- @usage function PLUGIN:CanPlayerEarnSalary(client, faction) -- return client:IsAdmin() -- Restricts earning salary to admins only. -- end function CanPlayerEarnSalary(client, faction) end --- Whether or not the player is allowed to enter observer mode. This is allowed only for admins by default and can be -- customized by server owners if the server is using a CAMI-compliant admin mod. -- @realm server -- @player client Player attempting to enter observer -- @treturn bool Whether or not to allow the player to enter observer -- @usage function PLUGIN:CanPlayerEnterObserver(client) -- return true -- Always allow observer. -- end function CanPlayerEnterObserver(client) end --- Whether or not a player can equip the given `item`. This is called for items with `outfit`, `pacoutfit`, or `weapons` as -- their base. Schemas/plugins can utilize this hook for their items. -- @realm server -- @player client Player attempting to equip the item -- @tab item Item being equipped -- @treturn bool Whether or not to allow the player to equip the item -- @see CanPlayerUnequipItem -- @usage function PLUGIN:CanPlayerEquipItem(client, item) -- return client:IsAdmin() -- Restrict equipping items to admins only. -- end function CanPlayerEquipItem(client, item) end --- Whether or not a player is allowed to hold an entity with the hands SWEP. -- @realm server -- @player client Player attempting to hold an entity -- @entity entity Entity being held -- @treturn bool Whether or not to allow the player to hold the entity -- @usage function PLUGIN:CanPlayerHoldObject(client, entity) -- return !(client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) -- Disallow players in observer holding objects. -- end function CanPlayerHoldObject(client, entity) end --- Whether or not a player is allowed to interact with an entity's interaction menu if it has one. -- @realm server -- @player client Player attempting interaction -- @entity entity Entity being interacted with -- @string option Option selected by the player -- @param data Any data passed with the interaction option -- @treturn bool Whether or not to allow the player to interact with the entity -- @usage function PLUGIN:CanPlayerInteractEntity(client, entity, option, data) -- if (entity:GetClass() == "my_big_entity" and entity:GetPos():Distance(client:GetPos()) < 192) then -- return true -- Force allow interacting if within larger than default interact range of large entity -- end -- -- if (client:GetNetVar("drunk")) then -- return false -- Disallow interacting with an entity while drunk -- end -- end function CanPlayerInteractEntity(client, entity, option, data) end --- Whether or not a player is allowed to interact with an item via an inventory action (e.g picking up, dropping, transferring -- inventories, etc). Note that this is for an item *table*, not an item *entity*. This is called after `CanPlayerDropItem` -- and `CanPlayerTakeItem`. -- @realm server -- @player client Player attempting interaction -- @string action The action being performed -- @param item Item's instance ID or item table -- @param data Any data passed with the action -- @treturn bool Whether or not to allow the player to interact with the item -- @usage function PLUGIN:CanPlayerInteractItem(client, action, item, data) -- return false -- Disallow interacting with any item. -- end function CanPlayerInteractItem(client, action, item, data) end --- Whether or not a plyer is allowed to join a class. -- @realm shared -- @player client Player attempting to join -- @number class ID of the class -- @tab info The class table -- @treturn bool Whether or not to allow the player to join the class -- @usage function PLUGIN:CanPlayerJoinClass(client, class, info) -- return client:IsAdmin() -- Restrict joining classes to admins only. -- end function CanPlayerJoinClass(client, class, info) end --- Whether or not a player can knock on the door with the hands SWEP. -- @realm server -- @player client Player attempting to knock -- @entity entity Door being knocked on -- @treturn bool Whether or not to allow the player to knock on the door -- @usage function PLUGIN:CanPlayerKnock(client, entity) -- return false -- Disable knocking on doors outright. -- end function CanPlayerKnock(client, entity) end --- Whether or not a player can open a shipment spawned from the business menu. -- @realm server -- @player client Player attempting to open the shipment -- @entity entity Shipment entity -- @treturn bool Whether or not to allow the player to open the shipment -- @usage function PLUGIN:CanPlayerOpenShipment(client, entity) -- return client:Team() == FACTION_BMD -- Restricts opening shipments to FACTION_BMD. -- end function CanPlayerOpenShipment(client, entity) end --- Whether or not a player is allowed to spawn a container entity. -- @realm server -- @player client Player attempting to spawn a container -- @string model Model of the container being spawned -- @entity entity Container entity -- @treturn bool Whether or not to allow the player to spawn the container -- @usage function PLUGIN:CanPlayerSpawnContainer(client, model, entity) -- return client:IsAdmin() -- Restrict spawning containers to admins. -- end function CanPlayerSpawnContainer(client, model, entity) end --- Whether or not a player is allowed to take an item and put it in their inventory. -- @realm server -- @player client Player attempting to take the item -- @entity item Entity corresponding to the item -- @treturn bool Whether or not to allow the player to take the item -- @usage function PLUGIN:CanPlayerTakeItem(client, item) -- return !(client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) -- Disallow players in observer taking items. -- end function CanPlayerTakeItem(client, item) end --- Whether or not the player is allowed to punch with the hands SWEP. -- @realm shared -- @player client Player attempting throw a punch -- @treturn bool Whether or not to allow the player to punch -- @usage function PLUGIN:CanPlayerThrowPunch(client) -- return client:GetCharacter():GetAttribute("str", 0) > 0 -- Only allow players with strength to punch. -- end function CanPlayerThrowPunch(client) end --- Whether or not a player can trade with a vendor. -- @realm server -- @player client Player attempting to trade -- @entity entity Vendor entity -- @string uniqueID The uniqueID of the item being traded. -- @bool isSellingToVendor If the client is selling to the vendor -- @treturn bool Whether or not to allow the client to trade with the vendor -- @usage function PLUGIN:CanPlayerTradeWithVendor(client, entity, uniqueID, isSellingToVendor) -- return false -- Disallow trading with vendors outright. -- end function CanPlayerTradeWithVendor(client, entity, uniqueID, isSellingToVendor) end --- Whether or not a player can unequip an item. -- @realm server -- @player client Player attempting to unequip an item -- @tab item Item being unequipped -- @treturn bool Whether or not to allow the player to unequip the item -- @see CanPlayerEquipItem -- @usage function PLUGIN:CanPlayerUnequipItem(client, item) -- return false -- Disallow unequipping items. -- end function CanPlayerUnequipItem(client, item) end --- Whether or not a player can buy an item from the business menu. -- @realm shared -- @player client Player that uses a business menu -- @string uniqueID The uniqueID of the business menu item -- @treturn bool Whether or not to allow the player to buy an item from the business menu -- @usage function PLUGIN:CanPlayerUseBusiness(client, uniqueID) -- return false -- Disallow buying from the business menu. -- end function CanPlayerUseBusiness(client, uniqueID) end --- Whether or not a player can use a character. -- @realm shared -- @player client Player that wants to use a character -- @char character Character that a player wants to use -- @treturn bool Whether or not to allow the player to load a character -- @usage function PLUGIN:CanPlayerUseCharacter(client, character) -- return false -- Disallow using any character. -- end function CanPlayerUseCharacter(client, character) end --- Whether or not a player can use a door. -- @realm server -- @player client Player that wants to use a door -- @entity entity Door that a player wants to use -- @treturn bool Whether or not to allow the player to use a door -- @usage function PLUGIN:CanPlayerUseDoor(client, character) -- return false -- Disallow using any door. -- end function CanPlayerUseDoor(client, entity) end --- Determines whether a player can use a vendor. -- @realm server -- @player activator The player attempting to use the vendor -- @entity vendor The vendor entity being used -- @treturn bool Returns false if the player can't use the vendor function CanPlayerUseVendor(activator, vendor) end --- Whether or not a player can view his inventory. -- @realm client -- @treturn bool Whether or not to allow the player to view his inventory -- @usage function PLUGIN:CanPlayerViewInventory() -- return false -- Prevent player from viewing his inventory. -- end function CanPlayerViewInventory() end --- Whether or not to save a container. -- @realm server -- @entity entity Container entity to save -- @tab inventory Container inventory -- @treturn bool Whether or not to save a container -- @usage function PLUGIN:CanSaveContainer(entity, inventory) -- return false -- Disallow saving any container. -- end function CanSaveContainer(entity, inventory) end --- @realm shared function CanTransferItem(item, currentInv, oldInv) end --- @realm shared function CharacterAttributeBoosted(client, character, attribID, boostID, boostAmount) end --- @realm shared function CharacterAttributeUpdated(client, self, key, value) end --- @realm shared function CharacterDeleted(client, id, isCurrentChar) end --- @realm shared function CharacterHasFlags(self, flags) end --- @realm shared function CharacterLoaded(character) end --- Called when a character was saved. -- @realm server -- @char character that was saved function CharacterPostSave(character) end --- @realm shared function CharacterPreSave(character) end --- @realm shared function CharacterRecognized() end --- Called when a character was restored. -- @realm server -- @char character that was restored function CharacterRestored(character) end --- @realm shared function CharacterVarChanged(character, key, oldVar, value) end --- @realm shared function CharacterVendorTraded(client, entity, uniqueID, isSellingToVendor) end --- @realm client function ChatboxCreated() end --- @realm client function ChatboxPositionChanged(x, y, width, height) end --- @realm client function ColorSchemeChanged(color) end --- Called when a container was removed. -- @realm server -- @entity container Container that was removed -- @tab inventory Container inventory function ContainerRemoved(container, inventory) end --- @realm client function CreateCharacterInfo(panel) end --- @realm client function CreateCharacterInfoCategory(panel) end --- @realm client function CreateItemInteractionMenu(icon, menu, itemTable) end --- @realm client function CreateMenuButtons(tabs) end --- Called when a shipment was created. -- @realm server -- @player client Player that ordered the shipment -- @entity entity Shipment entity function CreateShipment(client, entity) end --- Called when a server has connected to the database. -- @realm server function DatabaseConnected() end --- Called when a server failed to connect to the database. -- @realm server -- @string error Error that prevented server from connecting to the database function DatabaseConnectionFailed(error) end --- @realm shared function DoPluginIncludes(path, pluginTable) end --- @realm client function DrawCharacterOverview() end --- @realm client function DrawHelixModelView(panel, entity) end --- @realm client function DrawPlayerRagdoll(entity) end --- @realm client function GetCharacterDescription(client) end --- @realm shared function GetCharacterName(speaker, chatType) end --- @realm shared function GetChatPrefixInfo(text) end --- @realm client function GetCrosshairAlpha(curAlpha) end --- @realm shared function GetDefaultAttributePoints(client, count) end --- @realm shared function GetDefaultCharacterName(client, faction) end --- @realm shared function GetMaxPlayerCharacter(client) end --- Returns the sound to emit from the player upon death. If nothing is returned then it will use the default male/female death -- sounds. -- @realm server -- @player client Player that died -- @treturn[1] string Sound to play -- @treturn[2] bool `false` if a sound shouldn't be played at all -- @usage function PLUGIN:GetPlayerDeathSound(client) -- -- play impact sound every time someone dies -- return "physics/body/body_medium_impact_hard1.wav" -- end -- @usage function PLUGIN:GetPlayerDeathSound(client) -- -- don't play a sound at all -- return false -- end function GetPlayerDeathSound(client) end --- @realm client function GetPlayerEntityMenu(client, options) end --- @realm client function GetPlayerIcon(speaker) end --- Returns the sound to emit from the player upon getting damage. -- @realm server -- @player client Client that received damage -- @treturn string Sound to emit -- @usage function PLUGIN:GetPlayerPainSound(client) -- return "NPC_MetroPolice.Pain" -- Make players emit MetroPolice pain sound. -- end function GetPlayerPainSound(client) end --- @realm shared function GetPlayerPunchDamage(client, damage, context) end --- Returns the salary that character should get instead of his faction salary. -- @realm server -- @player client Client that is receiving salary -- @tab faction Faction of the player's character -- @treturn number Character salary -- @see CanPlayerEarnSalary -- @usage function PLUGIN:GetSalaryAmount(client, faction) -- return 0 -- Everyone get no salary. -- end function GetSalaryAmount(client, faction) end --- @realm client function GetTypingIndicator(character, text) end --- Registers chat classes after the core framework chat classes have been registered. You should usually create your chat -- classes in this hook - especially if you want to reference the properties of a framework chat class. -- @realm shared -- @usage function PLUGIN:InitializedChatClasses() -- -- let's say you wanted to reference an existing chat class's color -- ix.chat.Register("myclass", { -- format = "%s says \"%s\"", -- GetColor = function(self, speaker, text) -- -- make the chat class slightly brighter than the "ic" chat class -- local color = ix.chat.classes.ic:GetColor(speaker, text) -- -- return Color(color.r + 35, color.g + 35, color.b + 35) -- end, -- -- etc. -- }) -- end -- @see ix.chat.Register -- @see ix.chat.classes function InitializedChatClasses() end --- @realm shared function InitializedConfig() end --- @realm shared function InitializedPlugins() end --- @realm shared function InitializedSchema() end --- Called when an item was added to the inventory. -- @realm server -- @tab oldInv Previous item inventory -- @tab inventory New item inventory -- @tab item Item that was added to the inventory function InventoryItemAdded(oldInv, inventory, item) end --- Called when an item was removed from the inventory. -- @realm server -- @tab inventory Inventory from which item was removed -- @tab item Item that was removed from the inventory function InventoryItemRemoved(inventory, item) end --- @realm shared function IsCharacterRecognized(character, id) end --- @realm client function IsPlayerRecognized(client) end --- @realm client function IsRecognizedChatType(chatType) end --- Called when server is loading data. -- @realm server function LoadData() end --- @realm client function LoadFonts(font, genericFont) end --- @realm client function LoadIntro() end --- @realm client function MenuSubpanelCreated(subpanelName, panel) end --- @realm client function MessageReceived(client, info) end --- @realm client function OnAreaChanged(oldID, newID) end --- @realm shared function OnCharacterCreated(client, character) end --- Called when a player who uses a character has disconnected. -- @realm server -- @player client The player that has disconnected -- @char character The character that the player was using function OnCharacterDisconnect(client, character) end --- Called when a character was ragdolled or unragdolled. -- @realm server -- @player client Player that was ragdolled or unradolled -- @entity entity Ragdoll that represents the player -- @bool bFallenOver Whether or not the character was ragdolled or unragdolled function OnCharacterFallover(client, entity, bFallenOver) end --- Called when a character has gotten up from the ground. -- @realm server -- @player client Player that has gotten up -- @entity ragdoll Ragdoll used to represent the player function OnCharacterGetup(client, ragdoll) end --- @realm client function OnCharacterMenuCreated(panel) end --- Called whenever an item entity has spawned in the world. You can access the entity's item table with -- `entity:GetItemTable()`. -- @realm server -- @entity entity Spawned item entity -- @usage function PLUGIN:OnItemSpawned(entity) -- local item = entity:GetItemTable() -- -- do something with the item here -- end function OnItemSpawned(entity) end --- @realm shared function OnItemTransferred(item, curInv, inventory) end --- @realm client function OnLocalVarSet(key, var) end --- @realm client function OnPAC3PartTransferred(part) end --- Called when a player has picked up the money from the ground. -- @realm server -- @player client Player that picked up the money -- @entity self Money entity -- @treturn bool Whether or not to allow the player to pick up the money -- @usage function PLUGIN:OnPickupMoney(client, self) -- return false -- Disallow picking up money. -- end function OnPickupMoney(client, self) end --- @realm shared function OnPlayerAreaChanged(client, oldID, newID) end --- Called when a player has entered or exited the observer mode. -- @realm server -- @player client Player that entered or exited the observer mode -- @bool state Previous observer state function OnPlayerObserve(client, state) end --- Called when a player has selected the entity interaction menu option while interacting with a player. -- @realm server -- @player client Player that other player has interacted with -- @player callingClient Player that has interacted with with other player -- @string option Option that was selected function OnPlayerOptionSelected(client, callingClient, option) end --- Called when a player has purchased or sold a door. -- @realm server -- @player client Player that has purchased or sold a door -- @entity entity Door that was purchased or sold -- @bool bBuying Whether or not the player is bying a door -- @func bCallOnDoorChild Function to call something on the door child function OnPlayerPurchaseDoor(client, entity, bBuying, bCallOnDoorChild) end --- Called when a player was restricted. -- @realm server -- @player client Player that was restricted function OnPlayerRestricted(client) end --- Called when a player was unrestricted. -- @realm server -- @player client Player that was unrestricted function OnPlayerUnRestricted(client) end --- Called when a saved items were loaded. -- @realm server -- @tab loadedItems Table of items that were loaded function OnSavedItemLoaded(loadedItems) end --- Called when server database are being wiped. -- @realm server function OnWipeTables() end --- @realm shared function PlayerEnterSequence(client, sequence, callback, time, bNoFreeze) end --- Called when a player has interacted with an entity through the entity's interaction menu. -- @realm server -- @player client Player that performed interaction -- @entity entity Entity being interacted with -- @string option Option selected by the player -- @param data Any data passed with the interaction option function PlayerInteractEntity(client, entity, option, data) end --- Called when a player has interacted with an item. -- @realm server -- @player client Player that interacted with an item -- @string action Action selected by the player -- @tab item Item being interacted with function PlayerInteractItem(client, action, item) end --- Called when a player has joined a class. -- @realm server -- @player client Player that has joined a class -- @number class Index of the class player has joined to -- @number oldClass Index of the player's previous class function PlayerJoinedClass(client, class, oldClass) end --- @realm shared function PlayerLeaveSequence(entity) end --- Called when a player has loaded a character. -- @realm server -- @player client Player that has loaded a character -- @char character Character that was loaded -- @char currentChar Character that player was using function PlayerLoadedCharacter(client, character, currentChar) end --- Called when a player has locked a door. -- @realm server -- @player client Player that has locked a door -- @entity door Door that was locked -- @entity partner Door partner function PlayerLockedDoor(client, door, partner) end --- Called when a player has locked a vehicle. -- @realm server -- @player client Player that has locked a vehicle -- @entity vehicle Vehicle that was locked function PlayerLockedVehicle(client, vehicle) end --- Called when player has said something in the text chat. -- @realm server -- @player speaker Player that has said something in the text chat -- @string chatType Type of the chat that player used -- @string text Chat message that player send -- @bool anonymous Whether or not message was anonymous -- @tab receivers Players who will hear that message -- @string rawText Chat message without any formatting -- @treturn string You can return text that will be shown instead -- @usage function PLUGIN:PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText) -- return "Text" -- When a player writes something into chat, he will say "Text" instead. -- end function PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText) end --- Called when a player model was changed. -- @realm server -- @player client Player whose model was changed -- @string oldModel Old player model function PlayerModelChanged(client, oldModel) end --- Called when a player has got stamina. -- @realm server -- @player client Player who has got stamina function PlayerStaminaGained(client) end --- Called when a player has lost stamina. -- @realm server -- @player client Player who has lost stamina function PlayerStaminaLost(client) end --- @realm shared function PlayerThrowPunch(client, trace) end --- Called when a player has unlocked a door. -- @realm server -- @player client Player that has unlocked a door -- @entity door Door that was unlocked -- @entity partner Door partner function PlayerUnlockedDoor(client, door, partner) end --- Called when a player has unlocked a vehicle. -- @realm server -- @player client Player that has unlocked a vehicle -- @entity vehicle Vehicle that was unlocked function PlayerUnlockedVehicle(client, vehicle) end --- Called when a player has used an entity. -- @realm server -- @player client Player who has used an entity -- @entity entity Entity that was used by the player function PlayerUse(client, entity) end --- Called when a player has used a door. -- @realm server -- @player client Player who has used a door -- @entity entity Door that was used by the player function PlayerUseDoor(client, entity) end --- @realm shared function PlayerWeaponChanged(client, weapon) end --- @realm shared function PluginLoaded(uniqueID, pluginTable) end --- @realm shared function PluginShouldLoad(uniqueID) end --- @realm shared function PluginUnloaded(uniqueID) end --- @realm client function PopulateCharacterInfo(client, character, tooltip) end --- @realm client function PopulateEntityInfo(entity, tooltip) end --- @realm client function PopulateHelpMenu(categories) end --- @realm client function PopulateImportantCharacterInfo(entity, character, tooltip) end --- @realm client function PopulateItemTooltip(tooltip, item) end --- @realm client function PopulatePlayerTooltip(client, tooltip) end --- @realm client function PopulateScoreboardPlayerMenu(client, menu) end --- @realm client function PostChatboxDraw(width, height, alpha) end --- @realm client function PostDrawHelixModelView(panel, entity) end --- @realm client function PostDrawInventory(panel) end --- Called when server data was loaded. -- @realm server function PostLoadData() end --- Called after player loadout. -- @realm server -- @player client function PostPlayerLoadout(client) end --- Called after player has said something in the text chat. -- @realm server -- @player client Player that has said something in the text chat -- @string chatType Type of the chat that player used -- @string message Chat message that player send -- @bool anonymous Whether or not message was anonymous function PostPlayerSay(client, chatType, message, anonymous) end --- @realm shared function PostSetupActs() end --- Called before character deletion. -- @realm server -- @player client Character owner -- @char character Chraracter that will be deleted function PreCharacterDeleted(client, character) end --- Called before character loading. -- @realm server -- @player client Player that loading a character -- @char character Character that will be loaded -- @char currentChar Character that player is using function PrePlayerLoadedCharacter(client, character, currentChar) end --- Called before a message sent by a player is processed to be sent to other players - i.e this is ran as early as possible -- and before things like the auto chat formatting. Can be used to prevent the message from being sent at all. -- @realm server -- @player client Player sending the message -- @string chatType Chat class of the message -- @string message Contents of the message -- @bool bAnonymous Whether or not the player is sending the message anonymously -- @treturn bool Whether or not to prevent the message from being sent -- @usage function PLUGIN:PrePlayerMessageSend(client, chatType, message, bAnonymous) -- if (!client:IsAdmin()) then -- return false -- only allow admins to talk in chat -- end -- end function PrePlayerMessageSend(client, chatType, message, bAnonymous) end --- Called when server is saving data. -- @realm server function SaveData() end --- @realm client function ScreenResolutionChanged(width, height) end --- @realm shared function SetupActs() end --- @realm shared function SetupAreaProperties() end --- Called when a player has taken a shipment item. -- @realm server -- @player client Player that has taken a shipment item -- @string uniqueID UniqueID of the shipment item that was taken -- @number amount Amount of the items that were taken function ShipmentItemTaken(client, uniqueID, amount) end --- @realm client function ShouldBarDraw(bar) end --- Whether or not the server should delete saved items. -- @realm server -- @treturn bool Whether or not the server should delete saved items -- @usage function PLUGIN:ShouldDeleteSavedItems() -- return true -- Delete all saved items. -- end function ShouldDeleteSavedItems() end --- @realm client function ShouldDisplayArea(newID) end --- @realm client function ShouldDrawCrosshair(client, weapon) end --- @realm client function ShouldDrawItemSize(item) end --- @realm client function ShouldHideBars() end --- Whether or not a character should be permakilled upon death. This is only called if the `permakill` server config is -- enabled. -- @realm server -- @player client Player to permakill -- @char character Player's current character -- @entity inflictor Entity that inflicted the killing blow -- @entity attacker Other player or entity that killed the player -- @treturn bool `false` if the player should not be permakilled -- @usage function PLUGIN:ShouldPermakillCharacter(client, character, inflictor, attacker) -- if (client:IsAdmin()) then -- return false -- all non-admin players will have their character permakilled -- end -- end function ShouldPermakillCharacter(client, character, inflictor, attacker) end --- Whether or not player should drown. -- @realm server -- @player client Player that is underwater -- @treturn bool Whether or not player should drown -- @usage function PLUGIN:ShouldPlayerDrowned(client) -- return false -- Players will not drown. -- end function ShouldPlayerDrowned(client) end --- Whether or not remove player ragdoll on death. -- @realm server -- @player client Player that died -- @treturn bool Whether or not remove player ragdoll on death -- @usage function PLUGIN:ShouldRemoveRagdollOnDeath(client) -- return false -- Player ragdolls will not be removed. -- end function ShouldRemoveRagdollOnDeath(client) end --- Whether or not to restore character inventory. -- @realm server -- @number characterID ID of the character -- @number inventoryID ID of the inventory -- @string inventoryType Type of the inventory -- @treturn bool Whether or not to restore character inventory -- @usage function PLUGIN:ShouldRestoreInventory(characterID, inventoryID, inventoryType) -- return false -- Character inventories will not be restored. -- end function ShouldRestoreInventory(characterID, inventoryID, inventoryType) end --- @realm client function ShouldShowPlayerOnScoreboard(client) end --- Whether or not spawn player ragdoll on death. -- @realm server -- @player client Player that died -- @treturn bool Whether or not spawn player ragdoll on death -- @usage function PLUGIN:ShouldSpawnClientRagdoll(client) -- return false -- Player ragdolls will not be spawned. -- end function ShouldSpawnClientRagdoll(client) end --- @realm client function ShowEntityMenu(entity) end --- @realm client function ThirdPersonToggled(oldValue, value) end --- @realm client function UpdateCharacterInfo(panel, character) end --- @realm client function UpdateCharacterInfoCategory(panel, character) end --- Called when the distance on which the voice can be heard was changed. -- @realm server -- @number newValue New voice distance function VoiceDistanceChanged(newValue) end --- @realm client function WeaponCycleSound() end --- @realm client function WeaponSelectSound(weapon) end ================================================ FILE: docs/js/app.js ================================================ const skippedCategories = ["manual"]; class Node { constructor(name, element, expandable, noAutoCollapse, children = []) { this.name = name; this.element = element; this.expandable = expandable; this.noAutoCollapse = noAutoCollapse; this.children = children; } AddChild(name, element, expandable, noAutoCollapse, children) { let newNode = new Node(name, element, expandable, noAutoCollapse, children); this.children.push(newNode); return newNode; } } class SearchManager { constructor(input, contents) { this.input = input; this.input.addEventListener("input", event => { this.OnInputUpdated(this.input.value.toLowerCase().replace(/:/g, ".")); }); // setup search tree this.tree = new Node("", document.createElement("null"), true, true); this.entries = {}; const categoryElements = contents.querySelectorAll(".category"); // iterate each kind (hooks/libraries/classes/etc) for (const category of categoryElements) { const nameElement = category.querySelector(":scope > summary > h2"); if (!nameElement) { continue; } const categoryName = nameElement.textContent.trim().toLowerCase(); if (skippedCategories.includes(categoryName)) { continue; } let categoryNode = this.tree.AddChild(categoryName, category, true, true); const sectionElements = category.querySelectorAll(":scope > ul > li"); for (const section of sectionElements) { const entryElements = section.querySelectorAll(":scope > details > ul > li > a"); const sectionName = section.querySelector(":scope > details > summary > a") .textContent .trim() .toLowerCase(); let sectionNode = categoryNode.AddChild(sectionName, section.querySelector(":scope > details"), true); for (let i = 0; i < entryElements.length; i++) { const entryElement = entryElements[i]; const entryName = entryElement.textContent.trim().toLowerCase(); sectionNode.AddChild(sectionName + "." + entryName, entryElement.parentElement); } } } } ResetVisibility(current) { current.element.style.display = ""; if (current.noAutoCollapse) { current.element.open = true; } else if (current.expandable) { current.element.open = false; } for (let node of current.children) { this.ResetVisibility(node); } } Search(input, current) { let matched = false; if (current.name.indexOf(input) != -1) { matched = true; } for (let node of current.children) { let childMatched = this.Search(input, node); matched = matched || childMatched; } if (matched) { current.element.style.display = ""; if (current.expandable) { current.element.open = true; } } else { current.element.style.display = "none"; if (current.expandable) { current.element.open = false; } } return matched; } OnInputUpdated(input) { if (input.length <= 1) { this.ResetVisibility(this.tree); return; } this.Search(input, this.tree); } } window.onload = function() { const openDetails = document.querySelector(".category > ul > li > details[open]"); if (openDetails) { openDetails.scrollIntoView(); } } document.addEventListener("DOMContentLoaded", function() { const searchInput = document.getElementById("search"); const contents = document.querySelector("body > main > nav > section"); if (searchInput && contents) { new SearchManager(searchInput, contents); } }); ================================================ FILE: docs/manual/converting-from-clockwork.md ================================================ # Clockwork to Helix Migration If you are here, you probably want to be converting your code from another framework to Helix. Doing so should not be a difficult task. Most of the previous functions are probably within Helix in one form or another! This means all you need to do is match *x* function found in the old framework to *y* function in Helix. Some headings will contain a link - this will bring you to the documentation for Helix's equivalent library or class. This tutorial assumes basic to intermediate knowledge and experience with Garry's Mod Lua. **Before you start!** You will notice that Helix uses client for the variable that represents a player. Clockwork uses player for the variable instead, but this will conflict with the player library. So if you see `_player` being used in Clockwork, it means the Garry's Mod player library. This is just a preference and does not affect anything besides appear. So keep in mind throughout the tutorial, you may see player being used for Clockwork code and client being used for Helix code. They represent the same thing, just with a different name. If you are converting Clockwork code to Helix, keep in mind that `_player` is not defined so you will need to either define `_player` yourself or switch it to player instead and change the variable name to client for player objects. # Basics of Conversion ## Folders Clockwork code and file structure is not too different from Helix. In the schema, the plugins folder and schema folder stay in the same place. There are some minor differences in naming however: - The `schema/entities` folder should be moved outside out of the schema folder. - The `libraries` folder needs to be renamed to `libs` to load. - The `commands` tab will not load as each command is now defined in a single shared file, does not matter which one. ## Deriving from Helix This is pretty important. If you want to use Helix as the base, you need to set it as the base. So, go to your Clockwork schema's `gamemode` folder. Inside should be two files: `init.lua `and `cl_init.lua`. Open both, and you should see something along the lines of `DeriveGamemode("Clockwork")`. Change this to `DeriveGamemode("helix")`. # The Schema ## Introduction Inside of the `schema` folder of the actual schema, you should see a file named `sh_schema.lua`. This is the main schema file in both Clockwork and Helix. Most of your changes may actually be within this file. ## Including Files Both frameworks come with a utility function to include a file without worrying about sending them to the client and stuff. In Clockwork, this function is `Clockwork.kernel:IncludePrefixed("sh_myfile.lua")`. Change this to `ix.util.Include("sh_myfile.lua") `and save. # The Plugin ## Introduction Plugins serve as a means to add on to a schema or framework without directly modifying either. This allows for easier modifications that can be added/removed with ease. It is recommended that you keep all custom modifications left to plugins rather than editing the framework or the schema if possible. ## Structure All plugins in Clockwork and Helix go into the `plugins` folder. However, there are many differences with the CW plugin structure. First of all, there are two things you see when you open a plugin folder: `plugin` again and `plugin.ini`. Helix only has one file needed: `sh_plugin.lua` which acts like `sh_schema.lua` but for plugins. ## Conversion The first step is to move all of the contents from the `plugin` folder to the main folder of the plugin folder. The `sh_plugin.lua` file needs to be changed to provide basic information about the plugin.You need to define three things in `sh_plugin.lua` which can be found within the `plugin.ini` file: - `PLUGIN.name = "Plugin Name"` - `PLUGIN.author = "Plugin Author"` - `PLUGIN.description = "Plugin Description"` If the plugin uses a special variable (e.g. `cwPluginName`) for the plugin, change it to `PLUGIN`. - Note that the `PLUGIN` table is removed after the plugin is loaded. So if you want to use `PLUGIN` after the plugin has loaded (such as in console commands, in entities, etc.), add `local PLUGIN = PLUGIN` at the top. - You can see if a global variable is defined for it by looking for `PLUGIN:SetGlobalAlias("cwMyPlugin")`. So, one would change `cwMyPlugin` to `PLUGIN`. # The `Character` Object One main thing that is very notable is how the character is referenced using `client:GetCharacter()` which returns a character object. The way the object works is just like an entity you spawn. It has its own properties like the model, color, etc. that makes it unique. You can access all the characters in a table which stores loaded characters with `ix.char.loaded`. The character object comes with many predefined methods. You can look at how they are defined [by clicking here](https://github.com/NebulousCloud/helix/blob/master/gamemode/core/meta/sh_character.lua). The character object makes it very simple to manager character information. You will notice throughout the framework, the character object is used a lot. The use of the character object makes a large barrier between what belongs to the character and what belongs to the player. For example: flags, models, factions, data, and other things are stored on the character and can be accessed by the character object. In Clockwork, there is no use of an object. Instead, the character information is intertwined with the player object. For example: ``` -- in Clockwork player:SetCharacterData("foo", "bar") -- in Helix client:GetCharacter():SetData("foo", "bar") ``` The use of the character object allows you to access other characters a player might own without needing to have them be the active character, or even access them when the player is not on the server. Overall, the use of the character object may seem like a complex concept, but will simplify a lot of things once you get the hang of the idea. # The Libraries ## Animations (`ix.anim`) Clockwork features many functions to set up animations for a specific model. Helix too has this functionality. Helix has one function instead that pairs a model to a specific "animation class" (grouping of animation types). So, all one needs to do is find the appropriate animation class to match the model with. Looking at the Clockwork function name should tell you. ``` -- before Clockwork.animation:AddCivilProtectionModel("models/mymodel.mdl") -- after ix.anim.SetModelClass("models/mymodel.mdl", "metrocop") ``` ## Attributes (`ix.attributes`) Attributes allow the player to boost certain abilities over time. Both frameworks require one to register attributes, but they are done differently. In Clockwork, the `ATTRIBUTE` table needs to be defined and registered manually. In Helix, the `ATTRIBUTE` table is automatically defined and registered for you. All you need to do is have `ATTRIBUTE.value = "value"`. The basic parts of the attribute needed is `ATTRIBUTE.name` and `ATTRIBUTE.description`. One extra feature for attributes in Helix is `ATTRIBUTE:OnSetup(client, value)` which is a function that gets called on spawn to apply any effects. For example, the stamina attribute changes the player's run speed by adding the amount of stamina points the player has. You can find an example at [https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua](https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua) ## Classes (`ix.class`) Classes are a part of the factions. They basically are a more specific form of a faction. Factions in Helix and Clockwork work similarly. For instance, all classes are placed in the `classes` folder under the schema folder and use `CLASS` as the main variable inside the file. However: - You do not need to use `local CLASS = Clockwork.class:New("My Class")`. Instead, `CLASS` is already defined for you and you set the name using `CLASS.name = "My Class"` - `CLASS.factions` is *not* a table, so `CLASS.factions = {FACTION_MYFACTION}` becomes `CLASS.faction = FACTION_MYFACTION` - You do not need to use `CLASS:Register()` as classes are registered for you after the file is done processing. - Classes are *optional* for factions rather than being required. ## Commands (`ix.command`) Commands no longer need to be in separate files. Instead, they are just placed into one large file. However, if you really wanted you can register multiple commands across multiple files or however you want. One thing you may notice is Clockwork uses a _COMMAND_ table while Helix does not always. It is simply a design preference. You can find examples at [https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua](https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua) It should be noted that: - `COMMAND.tip` is not used. - `COMMAND.text` is not used. - `COMMAND.flags` is not used. - `COMMAND.arguments` does not need to be defined if no arguments are needed but is defined as a table of argument types when needed `arguments = {ix.type.character, ix.type.number}`. See `ix.command.CommandArgumentsStructure` for details. - `COMMAND.access` for checking whether or not a person is a (super)admin can be replaced with `adminOnly = true` or `superAdminOnly = true` in the command table. ## Configurations (`ix.config`) In Helix, the method of adding configurations that can be changed by server owners is heavily simplified. [See an example here](https://github.com/NebulousCloud/helix/blob/master/gamemode/config/sh_config.lua). Adding a configuration is as follows: ``` -- before Clockwork.config:Add("run_speed", 225) -- after ix.config.Add("runSpeed", 235, ...) ``` You'll notice that ellipses (...) were added at the end. This is because there are more arguments since adding configuration information has been placed into one function. Additionally: - `Clockwork.config:ShareKey()` is not needed. - The 3rd argument for `Clockwork.config:AddToSystem(name, key, description, min, max)` is also the 3rd argument for `ix.config.Add` - The 4th argument for `ix.config.Add` is an optional function that is called when the configuration is changed. - The 5th argument for `ix.config.Add` is a table. You can specify the category for the configuration to group it with other configurations. There is also a data table inside which can be used to determine the minimum value and maximum value for numbers. Check out [an example here](https://github.com/NebulousCloud/helix/blob/master/gamemode/config/sh_config.lua). See also `ix.config`. ## Currency (`ix.currency`) Updating your currency code is simple: ``` -- before Clockwork.config:SetKey("name_cash", "Tokens") Clockwork.config:SetKey("name_cash", "Dollars") -- another example -- after ix.currency.Set("", "token", "tokens") ix.currency.Set("$", "dollar", "dollars") ``` Note that you need to provide a symbol for that currency (€ for Euro, £ for Pound, ¥ for Yen, etc.) or just leave it as an empty string (`""`) and then provide the singular form of the name for the currency, then the plural form. ## Datastream Helix uses the [net library](http://wiki.garrysmod.com/page/Net_Library_Usage) whereas Clockwork uses datastream ([netstream](https://github.com/alexgrist/NetStream/blob/master/netstream2.lua)). If you're unfamiliar with the net library, you can include the netstream library to your schema by downloading [netstream](https://github.com/alexgrist/NetStream/blob/master/netstream2.lua) to `schema/libs/thirdparty/sh_netstream2.lua` and adding `ix.util.Include("libs/thirdparty/sh_netstream2.lua")` to your `sh_schema.lua` file. Starting a datastream: ``` -- before Clockwork.datastream:Start(receiver, "MessageName", {1, 2, 3}); -- after netstream.Start(receiver, "MessageName", 1, 2, 3) ``` Receiving a datastream: ``` -- before Clockwork.datastream:Hook("MessageName", function(player, data) local a = data[1]; local b = data[2]; local c = data[3]; print(a, b, c); end); -- after netstream.Hook("MessageName", function(client, a, b, c) print(a, b, c) end) ``` ## Factions (`ix.faction`) Factions, like classes, are pretty similar too. They share pretty much the same differences as classes in Clockwork and Helix do. For instance: - You do not need to use `local FACTION = Clockwork.faction:New("Name Here")`, instead `FACTION` is already defined for you and you set the name using `FACTION.name = "Name Here"` - `FACTION.whitelist = true` is changed to `FACTION.isDefault = false` - `FACTION.models` does not need a male and female part. Instead, all the models are combined into one big list. - `function FACTION:GetName(name)` becomes `function FACTION:GetDefaultName(name)` - `FACTION.description = "Describe me"` is added to the faction. - `FACTION_MYFACTION = FACTION:Register()` becomes `FACTION_MYFACTION = FACTION.index` ## Flags (`ix.flag`) Flags are functionally equivalent in Helix. To add a new flag: ``` -- before Clockwork.flag:Add("x", "Name", "Description") -- after ix.flag.Add("x", "Description") ``` To check or manipulate a character's flag(s): ``` -- before Clockwork.player:GiveFlags(player, flags) Clockwork.player:TakeFlags(player, flags) Clockwork.player:HasFlags(player, flags) -- after client:GetCharacter():GiveFlags(flags) client:GetCharacter():TakeFlags(flags) client:GetCharacter():HasFlags(flags) ``` ## Inventories (`Inventory`) Inventories have also had a change in the way they work that may seem very different than Clockwork. Similar to how characters are their own objects, inventories become their own objects as well. These inventory objects belong to character objects, which belongs to players. So, this creates a chain of objects which is neat. The use of inventories as objects makes it very simple to attach inventories to anything. To access a player's inventory, you need to use `client:GetCharacter():GetInventory()` which returns the main inventory object for the player's character. You can also access all loaded inventories with `ix.item.inventories` but that is not important right now. ## Items (`Item`) As discussed above, inventories contain items. Items are still used in inventories and world entities, use default class data, have callback functions, and can contain unique item data per instance. ### Setting up items Every time needs to be registered, or have information about it (such as the name, model, what it does, etc.) defined. In Clockwork, you have your items defined in schemas/plugins under the items folder. So let's start with the differences in structure in the item file. - `local ITEM = Clockwork.item:New();` is removed - `ITEM.uniqueID` is *completely* optional - Replace `ITEM.cost` with `ITEM.price` - `ITEM:Register()` is removed ### Item Sizes Helix's inventory uses a grid and utilizes width and height instead of weight as a means of inventory capacity. This means you will have to change your item's weight (`ITEM.weight`) to something that might be analagous to the item's size using `ITEM.width` and `ITEM.height`. The item's size must be at least one by one grid cell. It's up to you to balance the sizes of items in your use case - taking into account how many items a character might have at once, the default inventory size set in the config, etc. ### Item Functions Item functions are defined very differently than they are in Clockwork. For example: ``` -- before function ITEM:OnUse(player, entity) print("My name is: " .. player:Name(), entity) end -- after ITEM.functions.Use = { OnRun = function(item) print("My name is: " .. item.player, item.entity) end } ``` All item functions are defined in the `ITEM.functions` table. This allows the drop-down menus when using the item a lot easier and cleaner to generate dynamically. There is also more control of the icons used for the options, whether or not the function should be displayed, etc. You can see an example of a water item here: [https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua](https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua) Here, we can define what happens when the function is run, what the icon is, and what sound it plays when used. It is basically put into one area rather than being scattered among hooks and stuff. ### Giving/Taking Items So before we can give/take items, we need to understand what the *item instance* is. Using the analogy earlier about how the inventory system is like a forum, and inside the forum are posts (the items in this case), we can think of instancing an item as making a new post on a forum. So when we talk about an *item instance*, it is an item that has been created in the past. The reason we use an item instance (which is its own object too, neat!) is to make each item ever created unique. Each item instance can have its own data unique to itself. Clockwork also uses an item instance system where you have to instance an item. So, to instance an item in Clockwork you would use: ``` item = Clockwork.item:CreateInstance("item") ``` And this would create a new instance of an item. Helix's instancing system is slightly different. Instead of having the function return the instance like it does in Clockwork, Helix relies on a callback to pass the instance. The reason for this is the item must be inserted into the database to get a unique number to represent that item. This is not done instantly, otherwise servers would freeze when new items are made. Clockwork uses the time and adds a number to get the numeric ID for an item, which allows the item to be returned which "solves" the issue, but I digress. The Helix equivalent would be: ``` ix.item.Instance(0, "item", data, x, y, function(item) end) ``` Let's break down the differences: - For Helix's item instance, the 1st argument (`0`) is the inventory that the item belongs to. You can specify 0 so it does not belong to any inventory. - The data argument is *optional* and is just a table for the item data. - *x* and *y* are the position of the items in inventory. You can find an available *x* and *y* with `inventory:FindEmptySlot()`. - The function is an *optional* argument that passes the item instance. This is where you can directly access the new item. Keep in mind that Helix will simplify the item system for you when it can. Normally, you would not need to instance an item yourself unless you were doing something advanced. So you might be wondering, how do I spawn an item in the map, and how do I give a player an item? In Clockwork, you would do the following: ``` -- spawning an item in the map Clockwork.entity:CreateItem(player, Clockwork.item:CreateInstance("item"), Vector(1, 2, 3)); -- giving a player an item player:GiveItem(Clockwork.item:CreateInstance("item")); ``` The equivalent in Helix would be: ``` -- spawning an item in the map ix.item.Spawn("item", Vector(1, 2, 3)) -- giving a player an item client:GetCharacter():GetInventory():Add("test") ``` So in these two examples, the whole deal of instancing items is done for you in Helix! # Hooks You will need to modify the function name and arguments for your schema or plugin hooks. ``` -- before function Schema:PlayerPlayPainSound(player, gender, damageInfo, hitGroup) -- ... end -- after function Schema:GetPlayerPainSound(client) -- ... end ``` You can see the documented hooks for the schema and plugins in the `Plugin` section. # Conclusion Overall, most of the conversion from Clockwork to Helix is simply renaming a certain function and/or switching the order of arguments around. Both are frameworks so they function similarly. You may want to use our HL2 RP schema example for reference which can be found at [https://github.com/NebulousCloud/helix-hl2rp](https://github.com/NebulousCloud/helix-hl2rp) ================================================ FILE: docs/manual/getting-started.md ================================================ # Getting Started It's pretty easy to get started with creating your own schema with Helix. It requires a bit of bootstrapping if you're starting from scratch, but you should quickly be on your way to developing your schema after following one of the below sections in this guide. # Installing the framework Before you start working on your schema, you'll need to install Helix onto your server. The exact instructions will vary based on your server provider, or if you're hosting the server yourself. You'll need to download the framework from [GitHub](https://github.com/NebulousCloud/helix) into a folder called `helix`. This folder goes into your server's `gamemodes` folder at `garrysmod/gamemodes/helix`. That's it! The framework is now installed onto your server. Of course, you'll need to restart your server after installing the framework and a schema. # MySQL usage By default, Helix will use SQLite (which is built into Garry's Mod) to store player/character data. This requires no configuration and will work "out of the box" after installing and will be fine for most server owners. However, you might want to connect your database to your website or use multiple servers with one database - this will require the usage of an external database accessible elsewhere. This will require the use of a MySQL server. Some server providers will provide you with a MySQL database for free to use with your server. ## Installing Helix uses the [MySQLOO](https://github.com/FredyH/MySQLOO) library to connect to MySQL databases. You'll need to follow the instructions for installing that library onto your server before continuing. In a nutshell, you need to make sure `gmsv_mysqloo_win32.dll` or `gmsv_mysqloo_linux.dll` (depending on your server's operating system) is in the `garrysmod/lua/bin` folder. In older versions of MySQLOO, you previously required a .dll called `libmysql.dll` to place in your `root` server folder, where `srcds`/`srcds_linux` was stored. Newer versions of MySQLOO no longer require this. ## Configuring Now that you've installed MySQLOO, you need to tell Helix that you want to connect to an external MySQL database instead of using SQLite. This requires creating a `helix.yml` configuration file in the `garrysmod/gamemodes/helix` folder. There is an example one already made for you called `helix.example.yml` that you can copy and rename to `helix.yml`. The first thing you'll need to change is the `adapter` entry so that it says `mysqloo`. Next is to change the other entries to match your database's connection information. Here is an example of what your `helix.yml` should look like: ``` database: adapter: "mysqloo" hostname: "myexampledatabase.com" username: "myusername" password: "mypassword" database: "helix" port: 3306 ``` The `hostname` field can either be a domain name (like `myexampledatabase.com`) or an IP address (`123.123.123.123`). If you don't know what the `port` field should be, simply leave it as the default `3306`; this is the default port for MySQL database connections. The `database` field is the name of the database that you've created for Helix. Note that it does not need to be `helix`, it can be whatever you'd like. Another important thing to note about this configuration file is that you **must** indent with **two spaces only**. `database` should not have any spacing before it, and all other entries must have two spaces before them. Failing to ensure this will make the configuration file fail to load. # Starting with the HL2 RP schema (Basic) This section is for using the existing HL2 RP schema as a base for your own schema. It contains a good amount of example code if you need a stronger foundation than just a skeleton. First, you'll need to download the schema from [GitHub](https://github.com/NebulousCloud/helix-hl2rp). Make sure that you download the contents of the repository into a folder called `ixhl2rp` and place it into your `garrysmod/gamemodes` folder. That's all you'll need to do to get the schema installed, other than setting your gamemode to `ixhl2rp` in the server's command line. # Starting with the skeleton (Basic) If you don't want excess code you might not use, or prefer to build from an almost-empty foundation that covers the basic bootstrapping, then the skeleton schema is for you. The skeleton schema contains a lot of comments explaining why code is laid out in a certain way, and some other helpful tips/explanations. Make sure you give it a read! You'll need to download the schema from [GitHub](https://github.com/NebulousCloud/helix-skeleton) into the folder name of your choice - just make sure it's all lowercase with no spaces. Our example for the sake of brevity will be `myschema`. Place the folder into `garrysmod/gamemodes`. Next up is to modify the gamemode info so that Garry's Mod will properly recognize it. Rename `skeleton.txt` in your schema folder to your folder's name. In our example we would rename `skeleton.txt` to `myschema.txt`. Next, you'll need to modify the contents of `myschema.txt` and replace the existing information with your own - making sure to replace the `"skeleton"` at the top of the file to your folder's name. In our case we would replace it with `"myschema"`. Once you've renamed the file, you're all good to go! # Converting from Clockwork (Intermediate) If you are looking to switch to Helix from Clockwork, you can follow the @{converting-from-clockwork|conversion guide}. # Starting from scratch (Intermediate) You can always create the gamemode files yourself if you'd like (although we suggest the skeleton schema in general). In general, a schema is a gamemode that is derived from `helix` and automatically loads `schema/sh_schema.lua`. You shouldn't have your schema files outside of the `schema` folder. The files you'll need are as follows: `gamemode/init.lua` ``` AddCSLuaFile("cl_init.lua") DeriveGamemode("helix") ``` `gamemode/cl_init.lua` ``` DeriveGamemode("helix") ``` `schema/sh_schema.lua` ``` Schema.name = "My Schema" Schema.author = "me!" Schema.description = "My awesome schema." -- include your other schema files ix.util.Include("cl_schema.lua") ix.util.Include("sv_schema.lua") -- etc. ``` ================================================ FILE: docs/templates/landing.ltp ================================================

Helix Documentation

Welcome to the documentation for Helix - the better gamemode framework.

Developers

The sidebar shows the entire contents of the documentation. Libraries, functions, etc are all searchable with the search box at the top of the sidebar. Migrating from Clockwork? Check out the conversion guide in the manual.

Server owners

If you're looking to get your Helix server up and running as soon as possible, check out the Getting Started guide in the manual.

Community

Questions? Want to show off your work? Maybe drop a new plugin release? Come join our community Discord server.

Contributing

Helix is a large project and there are still a few things missing here and there. Contributions to the documentation - from function references, to simple typo fixes - are welcomed! Check out the ix.storage library's source code for a good example on how to write documentation. You'll need a basic understanding of Markdown, since it's used extensively to generate the markup.

If you'd like to contribute code, you can visit the GitHub repository and make a pull request.

Learning

Getting started on developing with the Helix framework requires an intermediate level of Garry's Mod Lua knowledge. You'll want to learn the basics before you get starting making a schema. The Garry's Mod Wiki is a good place to start.

================================================ FILE: docs/templates/ldoc.ltp ================================================ {% local baseUrl = ldoc.css:gsub("ldoc.css", "") local repo = "https://github.com/nebulouscloud/helix/" local pageTitle = mod and (ldoc.display_name(mod) .. " - " .. ldoc.title) or ldoc.title local oldmarkup = ldoc.markup function ldoc.markup(text, item) return oldmarkup(text, item, ldoc.plain) end function ldoc.url(path) return baseUrl .. path end function ldoc.realm_icon(realm) return ""; end function ldoc.is_kind_classmethod(kind) return kind ~= "libraries" end function ldoc.repo_reference(item) return repo .. "tree/master" .. item.file.filename:gsub(item.file.base, "/gamemode") .. "#L" .. item.lineno end local function moduleDescription(mod) if (mod.type == "topic") then return mod.body:gsub(mod.display_name, ""):gsub("#", ""):sub(1, 256) .. "..." end return mod.summary end %} {{pageTitle}} {% if (mod) then %} {% else %} {% end %}
{(docs/templates/sidebar.ltp)}
{% if (ldoc.root) then -- we're rendering the landing page (index.html) %} {(docs/templates/landing.ltp)} {% elseif (ldoc.body) then -- we're rendering non-code elements %}
{* ldoc.body *}
{% elseif (module) then -- we're rendering libary contents %}
{(docs/templates/module.ltp)}
{% end %}
================================================ FILE: docs/templates/module.ltp ================================================

{{mod.name}}

{* ldoc.markup(mod.summary) *}

{* ldoc.markup(mod.description) *}

{% for kind, items in mod.kinds() do %}

{{kind}}

{% for item in items() do %}

{* ldoc.realm_icon(item.tags.realm[1]) *}{{ldoc.display_name(item)}}

{% if (item.tags.internal) then %}
Internal

This is an internal function! You are able to use it, but you risk unintended side effects if used incorrectly.

{% end %} {% if (item.module and item.module.type ~= "hooks") then %} View source » {% end %} {% if (ldoc.descript(item):len() == 0) then %}
Incomplete

Documentation for this section is incomplete and needs expanding.

{% else %}

{* ldoc.markup(ldoc.descript(item)) *}

{% end %}
{# function arguments #} {% if (item.params and #item.params > 0) then %} {% local subnames = mod.kinds:type_of(item).subnames %} {% if (subnames) then %}

{{subnames}}

{% end %} {% for argument in ldoc.modules.iter(item.params) do %} {% local argument, sublist = item:subparam(argument) %} {% end %} {% end %} {# function returns #} {% if ((not ldoc.no_return_or_parms) and item.retgroups) then %} {% local groups = item.retgroups %}

Returns

{% end %} {% if (item.usage) then -- function usage %}

Example Usage

{% for usage in ldoc.modules.iter(item.usage) do %}
{* usage *}
{% end %} {% end %} {% if (item.see) then %}

See Also

{% end %}
{% end %} {% end %} ================================================ FILE: docs/templates/sidebar.ltp ================================================ {% local function isKindExpandable(kind) return kind ~= "Manual" end %} ================================================ FILE: entities/entities/ix_item.lua ================================================ AddCSLuaFile() ENT.Base = "base_entity" ENT.Type = "anim" ENT.PrintName = "Item" ENT.Category = "Helix" ENT.Spawnable = false ENT.ShowPlayerInteraction = true ENT.RenderGroup = RENDERGROUP_BOTH ENT.bNoPersist = true function ENT:SetupDataTables() self:NetworkVar("String", 0, "ItemID") end if (SERVER) then local invalidBoundsMin = Vector(-8, -8, -8) local invalidBoundsMax = Vector(8, 8, 8) util.AddNetworkString("ixItemEntityAction") function ENT:Initialize() self:SetModel("models/props_junk/watermelon01.mdl") self:SetSolid(SOLID_VPHYSICS) self:PhysicsInit(SOLID_VPHYSICS) self:SetUseType(SIMPLE_USE) self.health = 50 local physObj = self:GetPhysicsObject() if (IsValid(physObj)) then physObj:EnableMotion(true) physObj:Wake() end end function ENT:Use(activator, caller) local itemTable = self:GetItemTable() if (IsValid(caller) and caller:IsPlayer() and caller:GetCharacter() and itemTable) then itemTable.player = caller itemTable.entity = self if (itemTable.functions.take.OnCanRun(itemTable)) then caller:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client) if (!ix.item.PerformInventoryAction(client, "take", self)) then return false -- do not mark dirty if interaction fails end end) end itemTable.player = nil itemTable.entity = nil end end function ENT:SetItem(itemID) local itemTable = ix.item.instances[itemID] if (itemTable) then local material = itemTable:GetMaterial(self) self:SetSkin(itemTable:GetSkin()) self:SetModel(itemTable:GetModel()) if (material) then self:SetMaterial(material) end self:PhysicsInit(SOLID_VPHYSICS) self:SetSolid(SOLID_VPHYSICS) self:SetItemID(itemTable.uniqueID) self.ixItemID = itemID if (!table.IsEmpty(itemTable.data)) then self:SetNetVar("data", itemTable.data) end local physObj = self:GetPhysicsObject() if (!IsValid(physObj)) then self:PhysicsInitBox(invalidBoundsMin, invalidBoundsMax) self:SetCollisionBounds(invalidBoundsMin, invalidBoundsMax) end if (IsValid(physObj)) then physObj:EnableMotion(true) physObj:Wake() end if (itemTable.OnEntityCreated) then itemTable:OnEntityCreated(self) end end end function ENT:OnDuplicated(entTable) local itemID = entTable.ixItemID local itemTable = ix.item.instances[itemID] ix.item.Instance(0, itemTable.uniqueID, itemTable.data, 1, 1, function(item) self:SetItem(item:GetID()) end) end function ENT:OnTakeDamage(damageInfo) local itemTable = ix.item.instances[self.ixItemID] if (itemTable.OnEntityTakeDamage and itemTable:OnEntityTakeDamage(self, damageInfo) == false) then return end local damage = damageInfo:GetDamage() self:SetHealth(self:Health() - damage) if (self:Health() <= 0 and !self.ixIsDestroying) then self.ixIsDestroying = true self.ixDamageInfo = {damageInfo:GetAttacker(), damage, damageInfo:GetInflictor()} self:Remove() end end function ENT:OnRemove() if (!ix.shuttingDown and !self.ixIsSafe and self.ixItemID) then local itemTable = ix.item.instances[self.ixItemID] if (itemTable) then if (self.ixIsDestroying) then self:EmitSound("physics/cardboard/cardboard_box_break"..math.random(1, 3)..".wav") local position = self:LocalToWorld(self:OBBCenter()) local effect = EffectData() effect:SetStart(position) effect:SetOrigin(position) effect:SetScale(3) util.Effect("GlassImpact", effect) if (itemTable.OnDestroyed) then itemTable:OnDestroyed(self) end ix.log.Add(self.ixDamageInfo[1], "itemDestroy", itemTable:GetName(), itemTable:GetID()) end if (itemTable.OnRemoved) then itemTable:OnRemoved() end local query = mysql:Delete("ix_items") query:Where("item_id", self.ixItemID) query:Execute() end end end function ENT:Think() local itemTable = self:GetItemTable() if (!itemTable) then self:Remove() end if (itemTable.Think) then itemTable:Think(self) end return true end function ENT:UpdateTransmitState() return TRANSMIT_PVS end net.Receive("ixItemEntityAction", function(length, client) ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadEntity()) end) else ENT.PopulateEntityInfo = true local shadeColor = Color(0, 0, 0, 200) local blockSize = 4 local blockSpacing = 2 function ENT:OnPopulateEntityInfo(tooltip) local item = self:GetItemTable() if (!item) then return end local oldData = item.data item.data = self:GetNetVar("data", {}) item.entity = self ix.hud.PopulateItemTooltip(tooltip, item) local name = tooltip:GetRow("name") local color = name and name:GetBackgroundColor() or ix.config.Get("color") -- set the arrow to be the same colour as the title/name row tooltip:SetArrowColor(color) if ((item.width > 1 or item.height > 1) and hook.Run("ShouldDrawItemSize", item) != false) then local sizeHeight = item.height * blockSize + item.height * blockSpacing local size = tooltip:Add("Panel") size:SetWide(tooltip:GetWide()) if (tooltip:IsMinimal()) then size:SetTall(sizeHeight) size:Dock(TOP) size:SetZPos(-999) else size:SetTall(sizeHeight + 8) size:Dock(BOTTOM) end size.Paint = function(sizePanel, width, height) if (!tooltip:IsMinimal()) then surface.SetDrawColor(ColorAlpha(shadeColor, 60)) surface.DrawRect(0, 0, width, height) end local x, y = width * 0.5 - 1, height * 0.5 - 1 local itemWidth = item.width - 1 local itemHeight = item.height - 1 local heightDifference = ((itemHeight + 1) * blockSize + blockSpacing * itemHeight) x = x - (itemWidth * blockSize + blockSpacing * itemWidth) * 0.5 y = y - heightDifference * 0.5 for i = 0, itemHeight do for j = 0, itemWidth do local blockX, blockY = x + j * blockSize + j * blockSpacing, y + i * blockSize + i * blockSpacing surface.SetDrawColor(shadeColor) surface.DrawRect(blockX + 1, blockY + 1, blockSize, blockSize) surface.SetDrawColor(color) surface.DrawRect(blockX, blockY, blockSize, blockSize) end end end tooltip:SizeToContents() end item.entity = nil item.data = oldData end function ENT:DrawTranslucent() local itemTable = self:GetItemTable() if (itemTable and itemTable.DrawEntity) then itemTable:DrawEntity(self) end end function ENT:Draw() self:DrawModel() end end function ENT:GetEntityMenu(client) local itemTable = self:GetItemTable() local options = {} if (!itemTable) then return false end itemTable.player = client itemTable.entity = self for k, v in SortedPairs(itemTable.functions) do if (k == "take" or k == "combine") then continue end if (v.OnCanRun and v.OnCanRun(itemTable) == false) then continue end -- we keep the localized phrase since we aren't using the callbacks - the name won't matter in this case options[L(v.name or k)] = function() local send = true if (v.OnClick) then send = v.OnClick(itemTable) end if (v.sound) then surface.PlaySound(v.sound) end if (send != false) then net.Start("ixItemEntityAction") net.WriteString(k) net.WriteEntity(self) net.SendToServer() end -- don't run callbacks since we're handling it manually return false end end itemTable.player = nil itemTable.entity = nil return options end function ENT:GetItemTable() return ix.item.list[self:GetItemID()] end function ENT:GetData(key, default) local data = self:GetNetVar("data", {}) return data[key] or default end ================================================ FILE: entities/entities/ix_money.lua ================================================ AddCSLuaFile() ENT.Type = "anim" ENT.PrintName = "Money" ENT.Category = "Helix" ENT.Spawnable = false ENT.ShowPlayerInteraction = true ENT.bNoPersist = true function ENT:SetupDataTables() self:NetworkVar("Int", 0, "Amount") end if (SERVER) then local invalidBoundsMin = Vector(-8, -8, -8) local invalidBoundsMax = Vector(8, 8, 8) function ENT:Initialize() self:SetModel(ix.currency.model) self:SetSolid(SOLID_VPHYSICS) self:PhysicsInit(SOLID_VPHYSICS) self:SetUseType(SIMPLE_USE) local physObj = self:GetPhysicsObject() if (IsValid(physObj)) then physObj:EnableMotion(true) physObj:Wake() else self:PhysicsInitBox(invalidBoundsMin, invalidBoundsMax) self:SetCollisionBounds(invalidBoundsMin, invalidBoundsMax) end end function ENT:Use(activator) if (self.ixSteamID and self.ixCharID) then local char = activator:GetCharacter() if (char and self.ixCharID != char:GetID() and self.ixSteamID == activator:SteamID()) then activator:NotifyLocalized("itemOwned") return false end end activator:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client) if (hook.Run("OnPickupMoney", client, self) != false) then self:Remove() end end) end function ENT:UpdateTransmitState() return TRANSMIT_PVS end else ENT.PopulateEntityInfo = true function ENT:OnPopulateEntityInfo(container) local text = container:AddRow("name") text:SetImportant() text:SetText(ix.currency.Get(self:GetAmount())) text:SizeToContents() end end ================================================ FILE: entities/entities/ix_shipment.lua ================================================ AddCSLuaFile() ENT.Type = "anim" ENT.PrintName = "Shipment" ENT.Category = "Helix" ENT.Spawnable = false ENT.ShowPlayerInteraction = true ENT.bNoPersist = true function ENT:SetupDataTables() self:NetworkVar("Int", 0, "DeliveryTime") end if (SERVER) then function ENT:Initialize() self:SetModel("models/Items/item_item_crate.mdl") self:SetSolid(SOLID_VPHYSICS) self:PhysicsInit(SOLID_VPHYSICS) self:SetUseType(SIMPLE_USE) self:PrecacheGibs() local physObj = self:GetPhysicsObject() if (IsValid(physObj)) then physObj:EnableMotion(true) physObj:Wake() end self:SetDeliveryTime(CurTime() + 120) timer.Simple(120, function() if (IsValid(self)) then self:Remove() end end) end function ENT:Use(activator) activator:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client) if (client:GetCharacter() and client:GetCharacter():GetID() == self:GetNetVar("owner", 0) and hook.Run("CanPlayerOpenShipment", client, self) != false) then client.ixShipment = self net.Start("ixShipmentOpen") net.WriteEntity(self) net.WriteTable(self.items) net.Send(client) end -- don't mark dirty since the player could come back and use this shipment again later return false end) end function ENT:SetItems(items) self.items = items end function ENT:GetItemCount() local count = 0 for _, v in pairs(self.items) do count = count + math.max(v, 0) end return count end function ENT:OnRemove() self:EmitSound("physics/cardboard/cardboard_box_break"..math.random(1, 3)..".wav") local position = self:LocalToWorld(self:OBBCenter()) local effect = EffectData() effect:SetStart(position) effect:SetOrigin(position) effect:SetScale(3) util.Effect("GlassImpact", effect) end function ENT:UpdateTransmitState() return TRANSMIT_PVS end else ENT.PopulateEntityInfo = true local size = 150 local tempMat = Material("particle/warp1_warp", "alphatest") function ENT:Draw() local pos, ang = self:GetPos(), self:GetAngles() self:DrawModel() pos = pos + self:GetUp() * 25 pos = pos + self:GetForward() * 1 pos = pos + self:GetRight() * 3 local delTime = math.max(math.ceil(self:GetDeliveryTime() - CurTime()), 0) local func = function() surface.SetMaterial(tempMat) surface.SetDrawColor(0, 0, 0, 200) surface.DrawTexturedRect(-size / 2, -size / 2 - 10, size, size) ix.util.DrawText("k", 0, 0, color_white, 1, 4, "ixIconsBig") ix.util.DrawText(delTime, 0, -10, color_white, 1, 5, "ixBigFont") end cam.Start3D2D(pos, ang, .15) func() cam.End3D2D() ang:RotateAroundAxis(ang:Right(), 180) pos = pos - self:GetUp() * 26 cam.Start3D2D(pos, ang, .15) func() cam.End3D2D() end function ENT:OnPopulateEntityInfo(container) local owner = ix.char.loaded[self:GetNetVar("owner", 0)] local name = container:AddRow("name") name:SetImportant() name:SetText(L("shipment")) name:SizeToContents() if (owner) then local description = container:AddRow("description") description:SetText(L("shipmentDesc", owner:GetName())) description:SizeToContents() end end end ================================================ FILE: entities/weapons/ix_hands.lua ================================================ AddCSLuaFile() if (CLIENT) then SWEP.PrintName = "Hands" SWEP.Slot = 0 SWEP.SlotPos = 1 SWEP.DrawAmmo = false SWEP.DrawCrosshair = true end SWEP.Author = "Chessnut" SWEP.Instructions = [[Primary Fire: Throw/Punch Secondary Fire: Knock/Pickup Secondary Fire + Mouse: Rotate Object Reload: Drop]] SWEP.Purpose = "Hitting things and knocking on doors." SWEP.Drop = false SWEP.ViewModelFOV = 45 SWEP.ViewModelFlip = false SWEP.AnimPrefix = "rpg" SWEP.ViewTranslation = 4 if CLIENT then SWEP.NextAllowedPlayRateChange = 0 end SWEP.Primary.ClipSize = -1 SWEP.Primary.DefaultClip = -1 SWEP.Primary.Automatic = false SWEP.Primary.Ammo = "" SWEP.Primary.Damage = 5 SWEP.Primary.Delay = 0.75 SWEP.Secondary.ClipSize = -1 SWEP.Secondary.DefaultClip = 0 SWEP.Secondary.Automatic = false SWEP.Secondary.Ammo = "" SWEP.Secondary.Delay = 0.5 SWEP.ViewModel = Model("models/weapons/c_arms.mdl") SWEP.WorldModel = "" SWEP.UseHands = true SWEP.LowerAngles = Angle(0, 5, -14) SWEP.LowerAngles2 = Angle(0, 5, -19) SWEP.KnockViewPunchAngle = Angle(-1.3, 1.8, 0) SWEP.FireWhenLowered = true SWEP.HoldType = "fist" SWEP.holdDistance = 64 SWEP.maxHoldDistance = 96 -- how far away the held object is allowed to travel before forcefully dropping SWEP.maxHoldStress = 4000 -- how much stress the held object can undergo before forcefully dropping -- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER ACT_VM_FISTS_DRAW = 2 ACT_VM_FISTS_HOLSTER = 1 function SWEP:Initialize() self:SetHoldType(self.HoldType) self.lastHand = 0 self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2 self.heldObjectAngle = Angle(angle_zero) end if (CLIENT) then function SWEP:DoDrawCrosshair(x, y) surface.SetDrawColor(255, 255, 255, 66) surface.DrawRect(x - 2, y - 2, 4, 4) end hook.Add("CreateMove", "ixHandsCreateMove", function(cmd) if (LocalPlayer():GetLocalVar("bIsHoldingObject", false) and cmd:KeyDown(IN_ATTACK2)) then cmd:ClearMovement() local angle = RenderAngles() angle.z = 0 cmd:SetViewAngles(angle) end end) end function SWEP:Deploy() if (!IsValid(self:GetOwner())) then return end local viewModel = self:GetOwner():GetViewModel() if (IsValid(viewModel)) then viewModel:SetPlaybackRate(1) viewModel:ResetSequence(ACT_VM_FISTS_DRAW) if CLIENT then self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() end end self:DropObject() return true end function SWEP:Precache() util.PrecacheSound("npc/vort/claw_swing1.wav") util.PrecacheSound("npc/vort/claw_swing2.wav") util.PrecacheSound("physics/plastic/plastic_box_impact_hard1.wav") util.PrecacheSound("physics/plastic/plastic_box_impact_hard2.wav") util.PrecacheSound("physics/plastic/plastic_box_impact_hard3.wav") util.PrecacheSound("physics/plastic/plastic_box_impact_hard4.wav") util.PrecacheSound("physics/wood/wood_crate_impact_hard2.wav") util.PrecacheSound("physics/wood/wood_crate_impact_hard3.wav") end function SWEP:OnReloaded() self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2 self:DropObject() end function SWEP:Holster() if (!IsValid(self:GetOwner())) then return end local viewModel = self:GetOwner():GetViewModel() if (IsValid(viewModel)) then viewModel:SetPlaybackRate(1) viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER) if CLIENT then self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() end end return true end function SWEP:Think() if (!IsValid(self:GetOwner())) then return end if (CLIENT) then local viewModel = self:GetOwner():GetViewModel() if (IsValid(viewModel) and self.NextAllowedPlayRateChange < CurTime()) then viewModel:SetPlaybackRate(1) end else if (self:IsHoldingObject()) then local physics = self:GetHeldPhysicsObject() local bIsRagdoll = self.heldEntity:IsRagdoll() local holdDistance = bIsRagdoll and self.holdDistance * 0.5 or self.holdDistance local targetLocation = self:GetOwner():GetShootPos() + self:GetOwner():GetForward() * holdDistance if (bIsRagdoll) then targetLocation.z = math.min(targetLocation.z, self:GetOwner():GetShootPos().z - 32) end if (!IsValid(physics)) then self:DropObject() return end if (physics:GetPos():DistToSqr(targetLocation) > self.maxHoldDistanceSquared) then self:DropObject() else local physicsObject = self.holdEntity:GetPhysicsObject() local currentPlayerAngles = self:GetOwner():EyeAngles() local client = self:GetOwner() if (client:KeyDown(IN_ATTACK2)) then local cmd = client:GetCurrentCommand() self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Forward(), cmd:GetMouseX() / 15) self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Right(), cmd:GetMouseY() / 15) end self.lastPlayerAngles = self.lastPlayerAngles or currentPlayerAngles self.heldObjectAngle.y = self.heldObjectAngle.y - math.AngleDifference(self.lastPlayerAngles.y, currentPlayerAngles.y) self.lastPlayerAngles = currentPlayerAngles physicsObject:Wake() physicsObject:ComputeShadowControl({ secondstoarrive = 0.01, pos = targetLocation, angle = self.heldObjectAngle, maxangular = 256, maxangulardamp = 10000, maxspeed = 256, maxspeeddamp = 10000, dampfactor = 0.8, teleportdistance = self.maxHoldDistance * 0.75, deltatime = FrameTime() }) if (physics:GetStress() > self.maxHoldStress) then self:DropObject() end end end -- Prevents the camera from getting stuck when the object that the client is holding gets deleted. if(!IsValid(self.heldEntity) and self:GetOwner():GetLocalVar("bIsHoldingObject", true)) then self:GetOwner():SetLocalVar("bIsHoldingObject", false) end end end function SWEP:GetHeldPhysicsObject() return IsValid(self.heldEntity) and self.heldEntity:GetPhysicsObject() or nil end function SWEP:CanHoldObject(entity) local physics = entity:GetPhysicsObject() return IsValid(physics) and (physics:GetMass() <= ix.config.Get("maxHoldWeight", 100) and physics:IsMoveable()) and !self:IsHoldingObject() and !IsValid(entity.ixHeldOwner) and hook.Run("CanPlayerHoldObject", self:GetOwner(), entity) end function SWEP:IsHoldingObject() return IsValid(self.heldEntity) and IsValid(self.heldEntity.ixHeldOwner) and self.heldEntity.ixHeldOwner == self:GetOwner() end function SWEP:PickupObject(entity) if (self:IsHoldingObject() or !IsValid(entity) or !IsValid(entity:GetPhysicsObject())) then return end local physics = entity:GetPhysicsObject() physics:EnableGravity(false) physics:AddGameFlag(FVPHYSICS_PLAYER_HELD) entity.ixHeldOwner = self:GetOwner() entity.ixCollisionGroup = entity:GetCollisionGroup() entity:StartMotionController() entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) self.heldObjectAngle = entity:GetAngles() self.heldEntity = entity self.holdEntity = ents.Create("prop_physics") self.holdEntity:SetPos(self.heldEntity:LocalToWorld(self.heldEntity:OBBCenter())) self.holdEntity:SetAngles(self.heldEntity:GetAngles()) self.holdEntity:SetModel("models/weapons/w_bugbait.mdl") self.holdEntity:SetOwner(self:GetOwner()) self.holdEntity:SetNoDraw(true) self.holdEntity:SetNotSolid(true) self.holdEntity:SetCollisionGroup(COLLISION_GROUP_DEBRIS) self.holdEntity:DrawShadow(false) self.holdEntity:Spawn() local trace = self:GetOwner():GetEyeTrace() local physicsObject = self.holdEntity:GetPhysicsObject() if (IsValid(physicsObject)) then physicsObject:SetMass(2048) physicsObject:SetDamping(0, 1000) physicsObject:EnableGravity(false) physicsObject:EnableCollisions(false) physicsObject:EnableMotion(false) end if (trace.Entity:IsRagdoll()) then local tracedEnt = trace.Entity self.holdEntity:SetPos(tracedEnt:GetBonePosition(tracedEnt:TranslatePhysBoneToBone(trace.PhysicsBone))) end self.constraint = constraint.Weld(self.holdEntity, self.heldEntity, 0, trace.Entity:IsRagdoll() and trace.PhysicsBone or 0, 0, true, true) end function SWEP:DropObject(bThrow) if (!IsValid(self.heldEntity) or self.heldEntity.ixHeldOwner != self:GetOwner()) then return end self.lastPlayerAngles = nil self:GetOwner():SetLocalVar("bIsHoldingObject", false) self.constraint:Remove() self.holdEntity:Remove() self.heldEntity:StopMotionController() self.heldEntity:SetCollisionGroup(self.heldEntity.ixCollisionGroup or COLLISION_GROUP_NONE) local physics = self:GetHeldPhysicsObject() physics:EnableGravity(true) physics:Wake() physics:ClearGameFlag(FVPHYSICS_PLAYER_HELD) if (bThrow) then timer.Simple(0, function() if (IsValid(physics) and IsValid(self:GetOwner())) then physics:AddGameFlag(FVPHYSICS_WAS_THROWN) physics:ApplyForceCenter(self:GetOwner():GetAimVector() * ix.config.Get("throwForce", 732)) end end) end self.heldEntity.ixHeldOwner = nil self.heldEntity.ixCollisionGroup = nil self.heldEntity = nil end function SWEP:PlayPickupSound(surfaceProperty) local result = "Flesh.ImpactSoft" if (surfaceProperty != nil) then local surfaceName = util.GetSurfacePropName(surfaceProperty) local soundName = surfaceName:gsub("^metal$", "SolidMetal") .. ".ImpactSoft" if (sound.GetProperties(soundName)) then result = soundName end end self:GetOwner():EmitSound(result, 75, 100, 40) end function SWEP:Holster() if (!IsFirstTimePredicted() or CLIENT) then return end self:DropObject() return true end function SWEP:OnRemove() if (SERVER) then self:DropObject() end end function SWEP:OwnerChanged() if (SERVER) then self:DropObject() end end function SWEP:DoPunchAnimation() self.lastHand = math.abs(1 - self.lastHand) local sequence = 3 + self.lastHand local viewModel = self:GetOwner():GetViewModel() if (IsValid(viewModel)) then viewModel:SetPlaybackRate(0.5) viewModel:SetSequence(sequence) if CLIENT then self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2 end end end function SWEP:PrimaryAttack() if (!IsFirstTimePredicted()) then return end if (SERVER and self:IsHoldingObject()) then self:DropObject(true) return end self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) if (hook.Run("CanPlayerThrowPunch", self:GetOwner()) == false) then return end if (ix.plugin.Get("stamina")) then local staminaUse = ix.config.Get("punchStamina") if (staminaUse > 0) then local value = self:GetOwner():GetLocalVar("stm", 0) - staminaUse if (value < 0) then return elseif (SERVER) then self:GetOwner():ConsumeStamina(staminaUse) end end end if (SERVER) then self:GetOwner():EmitSound("npc/vort/claw_swing"..math.random(1, 2)..".wav") end self:DoPunchAnimation() self:GetOwner():SetAnimation(PLAYER_ATTACK1) self:GetOwner():ViewPunch(Angle(self.lastHand + 2, self.lastHand + 5, 0.125)) timer.Simple(0.055, function() if (IsValid(self) and IsValid(self:GetOwner())) then local damage = self.Primary.Damage local context = {damage = damage} local result = hook.Run("GetPlayerPunchDamage", self:GetOwner(), damage, context) if (result != nil) then damage = result else damage = context.damage end self:GetOwner():LagCompensation(true) local data = {} data.start = self:GetOwner():GetShootPos() data.endpos = data.start + self:GetOwner():GetAimVector() * 96 data.filter = self:GetOwner() local trace = util.TraceLine(data) if (SERVER and trace.Hit) then local entity = trace.Entity if (IsValid(entity)) then local damageInfo = DamageInfo() damageInfo:SetAttacker(self:GetOwner()) damageInfo:SetInflictor(self) damageInfo:SetDamage(damage) damageInfo:SetDamageType(DMG_GENERIC) damageInfo:SetDamagePosition(trace.HitPos) damageInfo:SetDamageForce(self:GetOwner():GetAimVector() * 1024) entity:DispatchTraceAttack(damageInfo, data.start, data.endpos) self:GetOwner():EmitSound("physics/body/body_medium_impact_hard"..math.random(1, 6)..".wav", 80) end end hook.Run("PlayerThrowPunch", self:GetOwner(), trace) self:GetOwner():LagCompensation(false) end end) end function SWEP:SecondaryAttack() if (!IsFirstTimePredicted()) then return end local data = {} data.start = self:GetOwner():GetShootPos() data.endpos = data.start + self:GetOwner():GetAimVector() * 84 data.filter = {self, self:GetOwner()} local trace = util.TraceLine(data) local entity = trace.Entity if CLIENT then local viewModel = self:GetOwner():GetViewModel() if (IsValid(viewModel)) then viewModel:SetPlaybackRate(0.5) if CLIENT then self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2 end end end if (SERVER and IsValid(entity)) then if (entity:IsDoor()) then if (hook.Run("CanPlayerKnock", self:GetOwner(), entity) == false) then return end self:GetOwner():ViewPunch(self.KnockViewPunchAngle) self:GetOwner():EmitSound("physics/wood/wood_crate_impact_hard"..math.random(2, 3)..".wav") self:GetOwner():SetAnimation(PLAYER_ATTACK1) self:DoPunchAnimation() self:SetNextSecondaryFire(CurTime() + 0.4) self:SetNextPrimaryFire(CurTime() + 1) elseif (entity:IsPlayer() and ix.config.Get("allowPush", true)) then local direction = self:GetOwner():GetAimVector() * (300 + (self:GetOwner():GetCharacter():GetAttribute("str", 0) * 3)) direction.z = 0 entity:SetVelocity(direction) self:GetOwner():EmitSound("Weapon_Crossbow.BoltHitBody") self:SetNextSecondaryFire(CurTime() + 1.5) self:SetNextPrimaryFire(CurTime() + 1.5) elseif (!entity:IsNPC() and self:CanHoldObject(entity)) then self:GetOwner():SetLocalVar("bIsHoldingObject", true) self:PickupObject(entity) self:PlayPickupSound(trace.SurfaceProps) self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) end end end function SWEP:Reload() if (!IsFirstTimePredicted()) then return end if (SERVER and IsValid(self.heldEntity)) then self:DropObject() end end ================================================ FILE: gamemode/cl_init.lua ================================================ -- unix systems are case-sensitive, are missing fonts, or use different naming conventions if (!system.IsWindows()) then local fontOverrides = { ["Roboto"] = "Roboto Regular", ["Roboto Th"] = "Roboto Thin", ["Roboto Lt"] = "Roboto Light", ["Roboto Bk"] = "Roboto Black", ["coolvetica"] = "Coolvetica", ["tahoma"] = "Tahoma", ["Harmonia Sans Pro Cyr"] = "Roboto Regular", ["Harmonia Sans Pro Cyr Light"] = "Roboto Light", ["Century Gothic"] = "Roboto Regular" } if (system.IsOSX()) then fontOverrides["Consolas"] = "Monaco" else fontOverrides["Consolas"] = "Courier New" end local ixCreateFont = surface.CreateFont function surface.CreateFont(name, info) -- luacheck: globals surface local font = info.font if (font and fontOverrides[font]) then info.font = fontOverrides[font] end ixCreateFont(name, info) end end DeriveGamemode("sandbox") ix = ix or {util = {}, gui = {}, meta = {}} -- Include core files. include("core/sh_util.lua") include("core/sh_data.lua") include("shared.lua") -- Sandbox stuff CreateConVar("cl_weaponcolor", "0.30 1.80 2.10", { FCVAR_ARCHIVE, FCVAR_USERINFO, FCVAR_DONTRECORD }, "The value is a Vector - so between 0-1 - not between 0-255") timer.Remove("HintSystem_OpeningMenu") timer.Remove("HintSystem_Annoy1") timer.Remove("HintSystem_Annoy2") ================================================ FILE: gamemode/config/sh_config.lua ================================================ -- You can change the default language by setting this in your schema. ix.config.language = "english" --[[ DO NOT CHANGE ANYTHING BELOW THIS. This is the Helix main configuration file. This file DOES NOT set any configurations, instead it just prepares them. To set the configuration, there is a "Config" tab in the F1 menu for super admins and above. Use the menu to change the variables, not this file. --]] ix.config.Add("maxCharacters", 5, "The maximum number of characters a player can have.", nil, { data = {min = 1, max = 50}, category = "characters" }) ix.config.Add("color", Color(75, 119, 190, 255), "The main color theme for the framework.", function(oldValue, newValue) if (newValue.a != 255) then ix.config.Set("color", ColorAlpha(newValue, 255)) return end if (CLIENT) then hook.Run("ColorSchemeChanged", newValue) end end, {category = "appearance"}) ix.config.Add("font", "Roboto Th", "The font used to display titles.", function(oldValue, newValue) if (CLIENT) then hook.Run("LoadFonts", newValue, ix.config.Get("genericFont")) end end, {category = "appearance"}) ix.config.Add("genericFont", "Roboto", "The font used to display generic texts.", function(oldValue, newValue) if (CLIENT) then hook.Run("LoadFonts", ix.config.Get("font"), newValue) end end, {category = "appearance"}) ix.config.Add("maxAttributes", 100, "The maximum amount each attribute can be.", nil, { data = {min = 0, max = 100}, category = "characters" }) ix.config.Add("chatAutoFormat", true, "Whether or not to automatically capitalize and punctuate in-character text.", nil, { category = "Chat" }) ix.config.Add("chatRange", 280, "The maximum distance a person's IC chat message goes to.", nil, { data = {min = 10, max = 5000, decimals = 1}, category = "chat" }) ix.config.Add("chatMax", 256, "The maximum amount of characters that can be sent in chat.", nil, { data = {min = 32, max = 1024}, category = "chat" }) ix.config.Add("chatColor", Color(255, 255, 150), "The default color for IC chat.", nil, {category = "chat"}) ix.config.Add("chatListenColor", Color(175, 255, 150), "The color for IC chat if you are looking at the speaker.", nil, { category = "chat" }) ix.config.Add("oocDelay", 10, "The delay before a player can use OOC chat again in seconds.", nil, { data = {min = 0, max = 10000}, category = "chat" }) ix.config.Add("allowGlobalOOC", true, "Whether or not Global OOC is enabled.", nil, { category = "chat" }) ix.config.Add("loocDelay", 0, "The delay before a player can use LOOC chat again in seconds.", nil, { data = {min = 0, max = 10000}, category = "chat" }) ix.config.Add("spawnTime", 5, "The time it takes to respawn.", nil, { data = {min = 0, max = 10000}, category = "characters" }) ix.config.Add("inventoryWidth", 6, "How many slots in a row there is in a default inventory.", nil, { data = {min = 0, max = 20}, category = "characters" }) ix.config.Add("inventoryHeight", 4, "How many slots in a column there is in a default inventory.", nil, { data = {min = 0, max = 20}, category = "characters" }) ix.config.Add("minNameLength", 4, "The minimum number of characters in a name.", nil, { data = {min = 4, max = 64}, category = "characters" }) ix.config.Add("maxNameLength", 32, "The maximum number of characters in a name.", nil, { data = {min = 16, max = 128}, category = "characters" }) ix.config.Add("minDescriptionLength", 16, "The minimum number of characters in a description.", nil, { data = {min = 0, max = 300}, category = "characters" }) ix.config.Add("saveInterval", 300, "How often characters save in seconds.", nil, { data = {min = 60, max = 3600}, category = "characters" }) ix.config.Add("walkSpeed", 130, "How fast a player normally walks.", function(oldValue, newValue) for _, v in player.Iterator() do v:SetWalkSpeed(newValue) end end, { data = {min = 75, max = 500}, category = "characters" }) ix.config.Add("runSpeed", 235, "How fast a player normally runs.", function(oldValue, newValue) for _, v in player.Iterator() do v:SetRunSpeed(newValue) end end, { data = {min = 75, max = 500}, category = "characters" }) ix.config.Add("walkRatio", 0.5, "How fast one goes when holding ALT.", nil, { data = {min = 0, max = 1, decimals = 1}, category = "characters" }) ix.config.Add("intro", true, "Whether or not the Helix intro is enabled for new players.", nil, { category = "appearance" }) ix.config.Add("music", "music/hl2_song2.mp3", "The default music played in the character menu.", nil, { category = "appearance" }) ix.config.Add("communityURL", "https://nebulous.cloud/", "The URL to navigate to when the community button is clicked.", nil, { category = "appearance" }) ix.config.Add("communityText", "@community", "The text to display on the community button. You can use language phrases by prefixing with @", nil, { category = "appearance" }) ix.config.Add("vignette", true, "Whether or not the vignette is shown.", nil, { category = "appearance" }) ix.config.Add("scoreboardRecognition", false, "Whether or not recognition is used in the scoreboard.", nil, { category = "characters" }) ix.config.Add("defaultMoney", 0, "The amount of money that players start with.", nil, { category = "characters", data = {min = 0, max = 1000} }) ix.config.Add("minMoneyDropAmount", 1, "The minimum amount of money that can be dropped.", nil, { category = "characters", data = {min = 1, max = 1000} }) ix.config.Add("allowVoice", false, "Whether or not voice chat is allowed.", function(oldValue, newValue) if (SERVER) then hook.Run("VoiceToggled", newValue) end end, { category = "server" }) ix.config.Add("voiceDistance", 600.0, "How far can the voice be heard.", function(oldValue, newValue) if (SERVER) then hook.Run("VoiceDistanceChanged", newValue) end end, { category = "server", data = {min = 0, max = 5000, decimals = 1} }) ix.config.Add("weaponAlwaysRaised", false, "Whether or not weapons are always raised.", nil, { category = "server" }) ix.config.Add("weaponRaiseTime", 1, "The time it takes for a weapon to raise.", nil, { data = {min = 0.1, max = 60, decimals = 1}, category = "server" }) ix.config.Add("allowBusiness", true, "Whether or not business is enabled.", nil, { category = "server" }) ix.config.Add("maxHoldWeight", 100, "The maximum weight that a player can carry in their hands.", nil, { data = {min = 1, max = 500}, category = "interaction" }) ix.config.Add("throwForce", 732, "How hard a player can throw the item that they're holding.", nil, { data = {min = 0, max = 8192}, category = "interaction" }) ix.config.Add("allowPush", true, "Whether or not pushing with hands is allowed.", nil, { category = "interaction" }) ix.config.Add("itemPickupTime", 0.5, "How long it takes to pick up and put an item in your inventory.", nil, { data = {min = 0, max = 5, decimals = 1}, category = "interaction" }) ix.config.Add("year", 2015, "The current in-game year.", function(oldValue, newValue) if (SERVER and !ix.date.bSaving) then ix.date.ResolveOffset() ix.date.current:setyear(newValue) ix.date.Send() end end, { data = {min = 1, max = 9999}, category = "date" }) ix.config.Add("month", 1, "The current in-game month.", function(oldValue, newValue) if (SERVER and !ix.date.bSaving) then ix.date.ResolveOffset() ix.date.current:setmonth(newValue) ix.date.Send() end end, { data = {min = 1, max = 12}, category = "date" }) ix.config.Add("day", 1, "The current in-game day.", function(oldValue, newValue) if (SERVER and !ix.date.bSaving) then ix.date.ResolveOffset() ix.date.current:setday(newValue) ix.date.Send() end end, { data = {min = 1, max = 31}, category = "date" }) ix.config.Add("secondsPerMinute", 60, "How many seconds it takes for a minute to pass in-game.", function(oldValue, newValue) if (SERVER and !ix.date.bSaving) then ix.date.UpdateTimescale(newValue) ix.date.Send() end end, { data = {min = 0.01, max = 120}, category = "date" }) ================================================ FILE: gamemode/config/sh_options.lua ================================================ if (CLIENT) then ix.option.Add("animationScale", ix.type.number, 1, { category = "appearance", min = 0.3, max = 2, decimals = 1 }) ix.option.Add("24hourTime", ix.type.bool, false, { category = "appearance" }) ix.option.Add("altLower", ix.type.bool, true, { category = "general" }) ix.option.Add("alwaysShowBars", ix.type.bool, false, { category = "appearance" }) ix.option.Add("minimalTooltips", ix.type.bool, false, { category = "appearance" }) ix.option.Add("noticeDuration", ix.type.number, 8, { category = "appearance", min = 0.1, max = 20, decimals = 1 }) ix.option.Add("noticeMax", ix.type.number, 4, { category = "appearance", min = 1, max = 20 }) ix.option.Add("cheapBlur", ix.type.bool, false, { category = "performance" }) ix.option.Add("disableAnimations", ix.type.bool, false, { category = "performance" }) ix.option.Add("openBags", ix.type.bool, true, { category = "general" }) ix.option.Add("showIntro", ix.type.bool, true, { category = "general" }) ix.option.Add("escCloseMenu", ix.type.bool, false, { category = "general" }) end ix.option.Add("language", ix.type.array, ix.config.language or "english", { category = "general", bNetworked = true, populate = function() local entries = {} for k, _ in SortedPairs(ix.lang.stored) do local name = ix.lang.names[k] local name2 = k:utf8sub(1, 1):utf8upper() .. k:utf8sub(2) if (name) then name = name .. " (" .. name2 .. ")" else name = name2 end entries[k] = name end return entries end }) ================================================ FILE: gamemode/core/cl_skin.lua ================================================ local gradient = surface.GetTextureID("vgui/gradient-d") local gradientUp = surface.GetTextureID("vgui/gradient-u") local gradientLeft = surface.GetTextureID("vgui/gradient-l") local gradientRadial = Material("helix/gui/radial-gradient.png") local defaultBackgroundColor = Color(30, 30, 30, 200) local SKIN = {} derma.DefineSkin("helix", "The base skin for the Helix framework.", SKIN) SKIN.fontCategory = "ixMediumLightFont" SKIN.fontCategoryBlur = "ixMediumLightBlurFont" SKIN.fontSegmentedProgress = "ixMediumLightFont" SKIN.Colours = table.Copy(derma.SkinList.Default.Colours) SKIN.Colours.Info = Color(100, 185, 255) SKIN.Colours.Success = Color(64, 185, 85) SKIN.Colours.Error = Color(255, 100, 100) SKIN.Colours.Warning = Color(230, 180, 0) SKIN.Colours.MenuLabel = color_white SKIN.Colours.DarkerBackground = Color(0, 0, 0, 77) SKIN.Colours.SegmentedProgress = {} SKIN.Colours.SegmentedProgress.Bar = Color(64, 185, 85) SKIN.Colours.SegmentedProgress.Text = color_white SKIN.Colours.Area = {} SKIN.Colours.Window.TitleActive = Color(0, 0, 0) SKIN.Colours.Window.TitleInactive = color_white SKIN.Colours.Button.Normal = color_white SKIN.Colours.Button.Hover = color_white SKIN.Colours.Button.Down = Color(180, 180, 180) SKIN.Colours.Button.Disabled = Color(0, 0, 0, 100) SKIN.Colours.Label.Default = color_white function SKIN.tex.Menu_Strip(x, y, width, height, color) surface.SetDrawColor(0, 0, 0, 200) surface.DrawRect(x, y, width, height) surface.SetDrawColor(ColorAlpha(color or ix.config.Get("color"), 175)) surface.SetTexture(gradient) surface.DrawTexturedRect(x, y, width, height) surface.SetTextColor(color_white) end hook.Add("ColorSchemeChanged", "ixSkin", function(color) SKIN.Colours.Area.Background = color end) function SKIN:DrawHelixCurved(x, y, radius, segments, barHeight, fraction, color, altColor) radius = radius or math.min(ScreenScale(72), 128) * 2 segments = segments or 76 barHeight = barHeight or 64 color = color or ix.config.Get("color") altColor = altColor or Color(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) fraction = fraction or 1 surface.SetTexture(-1) for i = 1, math.ceil(segments) do local angle = math.rad((i / segments) * -360) local barX = x + math.sin(angle + (fraction * math.pi * 2)) * radius local barY = y + math.cos(angle + (fraction * math.pi * 2)) * radius local barOffset = math.sin(SysTime() + i * 0.5) if (barOffset > 0) then surface.SetDrawColor(color) else surface.SetDrawColor(altColor) end surface.DrawTexturedRectRotated(barX, barY, 4, barOffset * (barHeight * fraction), math.deg(angle)) end end function SKIN:DrawHelix(x, y, width, height, segments, color, fraction, speed) segments = segments or width * 0.05 color = color or ix.config.Get("color") fraction = fraction or 0.25 speed = speed or 1 for i = 1, math.ceil(segments) do local offset = math.sin((SysTime() + speed) + i * fraction) local barHeight = height * offset surface.SetTexture(-1) if (offset > 0) then surface.SetDrawColor(color) else surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) end surface.DrawTexturedRectRotated(x + (i / segments) * width, y + height * 0.5, 4, barHeight, 0) end end function SKIN:PaintFrame(panel) if (!panel.bNoBackgroundBlur) then ix.util.DrawBlur(panel, 10) end surface.SetDrawColor(30, 30, 30, 150) surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall()) if (panel:GetTitle() != "" or panel.btnClose:IsVisible()) then surface.SetDrawColor(ix.config.Get("color")) surface.DrawRect(0, 0, panel:GetWide(), 24) if (panel.bHighlighted) then self:DrawImportantBackground(0, 0, panel:GetWide(), 24, ColorAlpha(color_white, 22)) end end surface.SetDrawColor(ix.config.Get("color")) surface.DrawOutlinedRect(0, 0, panel:GetWide(), panel:GetTall()) end function SKIN:PaintBaseFrame(panel, width, height) if (!panel.bNoBackgroundBlur) then ix.util.DrawBlur(panel, 10) end surface.SetDrawColor(30, 30, 30, 150) surface.DrawRect(0, 0, width, height) surface.SetDrawColor(ix.config.Get("color")) surface.DrawOutlinedRect(0, 0, width, height) end function SKIN:DrawImportantBackground(x, y, width, height, color) color = color or defaultBackgroundColor surface.SetTexture(gradientLeft) surface.SetDrawColor(color) surface.DrawTexturedRect(x, y, width, height) end function SKIN:DrawCharacterStatusBackground(panel, fraction) surface.SetDrawColor(0, 0, 0, fraction * 100) surface.DrawRect(0, 0, ScrW(), ScrH()) ix.util.DrawBlurAt(0, 0, ScrW(), ScrH(), 5, nil, fraction * 255) end function SKIN:PaintPanel(panel) if (panel.m_bBackground) then local width, height = panel:GetSize() if (panel.m_bgColor) then surface.SetDrawColor(panel.m_bgColor) else surface.SetDrawColor(30, 30, 30, 100) end surface.DrawRect(0, 0, width, height) surface.SetDrawColor(0, 0, 0, 150) surface.DrawOutlinedRect(0, 0, width, height) end end function SKIN:PaintMenuBackground(panel, width, height, alphaFraction) alphaFraction = alphaFraction or 1 surface.SetDrawColor(ColorAlpha(color_black, alphaFraction * 255)) surface.SetTexture(gradient) surface.DrawTexturedRect(0, 0, width, height) ix.util.DrawBlur(panel, alphaFraction * 15, nil, 200) end function SKIN:PaintPlaceholderPanel(panel, width, height, barWidth, padding) local size = math.max(width, height) barWidth = barWidth or size * 0.05 local segments = size / barWidth for i = 1, segments do surface.SetTexture(-1) surface.SetDrawColor(0, 0, 0, 88) surface.DrawTexturedRectRotated(i * barWidth, i * barWidth, barWidth, size * 2, -45) end end function SKIN:PaintCategoryPanel(panel, text, color) text = text or "" color = color or ix.config.Get("color") surface.SetFont(self.fontCategoryBlur) local textHeight = select(2, surface.GetTextSize(text)) + 6 local width, height = panel:GetSize() surface.SetDrawColor(0, 0, 0, 100) surface.DrawRect(0, textHeight, width, height - textHeight) self:DrawImportantBackground(0, 0, width, textHeight, color) surface.SetTextColor(color_black) surface.SetTextPos(4, 4) surface.DrawText(text) surface.SetFont(self.fontCategory) surface.SetTextColor(color_white) surface.SetTextPos(4, 4) surface.DrawText(text) surface.SetDrawColor(color) surface.DrawOutlinedRect(0, 0, width, height) return 1, textHeight, 1, 1 end function SKIN:PaintButton(panel) if (panel.m_bBackground) then local w, h = panel:GetWide(), panel:GetTall() local alpha = 50 if (panel:GetDisabled()) then alpha = 10 elseif (panel.Depressed) then alpha = 180 elseif (panel.Hovered) then alpha = 75 end if (panel:GetParent() and panel:GetParent():GetName() == "DListView_Column") then surface.SetDrawColor(color_white) surface.DrawRect(0, 0, w, h) end surface.SetDrawColor(30, 30, 30, alpha) surface.DrawRect(0, 0, w, h) surface.SetDrawColor(0, 0, 0, 180) surface.DrawOutlinedRect(0, 0, w, h) surface.SetDrawColor(180, 180, 180, 2) surface.DrawOutlinedRect(1, 1, w - 2, h - 2) end end function SKIN:PaintEntityInfoBackground(panel, width, height) ix.util.DrawBlur(panel, 1) surface.SetDrawColor(self.Colours.DarkerBackground) surface.DrawRect(0, 0, width, height) end function SKIN:PaintTooltipBackground(panel, width, height) ix.util.DrawBlur(panel, 1) surface.SetDrawColor(self.Colours.DarkerBackground) surface.DrawRect(0, 0, width, height) end function SKIN:PaintTooltipMinimalBackground(panel, width, height) surface.SetDrawColor(0, 0, 0, 150 * panel.fraction) surface.SetMaterial(gradientRadial) surface.DrawTexturedRect(0, 0, width, height) end function SKIN:PaintSegmentedProgressBackground(panel, width, height) end function SKIN:PaintSegmentedProgress(panel, width, height) local font = panel:GetFont() or self.fontSegmentedProgress local textColor = panel:GetTextColor() or self.Colours.SegmentedProgress.Text local barColor = panel:GetBarColor() or self.Colours.SegmentedProgress.Bar local segments = panel:GetSegments() local segmentHalfWidth = width / #segments * 0.5 surface.SetDrawColor(barColor) surface.DrawRect(0, 0, panel:GetFraction() * width, height) for i = 1, #segments do local text = segments[i] local x = (i - 1) / #segments * width + segmentHalfWidth local y = height * 0.5 draw.SimpleText(text, font, x, y, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end end function SKIN:PaintCharacterCreateBackground(panel, width, height) surface.SetDrawColor(40, 40, 40, 255) surface.SetTexture(gradient) surface.DrawTexturedRect(0, 0, width, height) end function SKIN:PaintCharacterLoadBackground(panel, width, height) surface.SetDrawColor(40, 40, 40, panel:GetBackgroundFraction() * 255) surface.SetTexture(gradient) surface.DrawTexturedRect(0, 0, width, height) end function SKIN:PaintCharacterTransitionOverlay(panel, x, y, width, height, color) color = color or ix.config.Get("color") surface.SetDrawColor(color) surface.DrawRect(x, y, width, height) end function SKIN:PaintAreaEntry(panel, width, height) local color = ColorAlpha(panel:GetBackgroundColor() or self.Colours.Area.Background, panel:GetBackgroundAlpha()) self:DrawImportantBackground(0, 0, width, height, color) end function SKIN:PaintListRow(panel, width, height) surface.SetDrawColor(0, 0, 0, 150) surface.DrawRect(0, 0, width, height) end function SKIN:PaintSettingsRowBackground(panel, width, height) local index = panel:GetBackgroundIndex() local bReset = panel:GetShowReset() if (index == 0) then surface.SetDrawColor(30, 30, 30, 45) surface.DrawRect(0, 0, width, height) end if (bReset) then surface.SetDrawColor(self.Colours.Warning) surface.DrawRect(0, 0, 2, height) end end function SKIN:PaintVScrollBar(panel, width, height) end function SKIN:PaintHScrollBar(panel, width, height) end function SKIN:PaintScrollBarGrip(panel, width, height) local parent = panel:GetParent() if (IsValid(parent.btnUp)) then -- Vertical scrollbar local upButtonHeight = parent.btnUp:GetTall() local downButtonHeight = parent.btnDown:GetTall() DisableClipping(true) surface.SetDrawColor(30, 30, 30, 200) surface.DrawRect(4, -upButtonHeight, width - 8, height + upButtonHeight + downButtonHeight) DisableClipping(false) else -- Horizontal scrollbar local leftButtonWidth = parent.btnLeft:GetWide() local rightButtonWidth = parent.btnRight:GetWide() DisableClipping(true) surface.SetDrawColor(30, 30, 30, 200) surface.DrawRect(-leftButtonWidth, 4, width + leftButtonWidth + rightButtonWidth, height - 8) DisableClipping(false) end end function SKIN:PaintButtonUp(panel, width, height) end function SKIN:PaintButtonDown(panel, width, height) end function SKIN:PaintButtonLeft(panel, width, height) end function SKIN:PaintButtonRight(panel, width, height) end function SKIN:PaintComboBox(panel, width, height) end function SKIN:PaintComboDownArrow(panel, width, height) surface.SetFont("ixIconsSmall") local textWidth, textHeight = surface.GetTextSize("r") local alpha = (panel.ComboBox:IsMenuOpen() or panel.ComboBox.Hovered) and 200 or 100 surface.SetTextColor(ColorAlpha(ix.config.Get("color"), alpha)) surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5) surface.DrawText("r") end function SKIN:PaintMenu(panel, width, height) ix.util.DrawBlur(panel) surface.SetDrawColor(30, 30, 30, 150) surface.DrawRect(0, 0, width, height) end function SKIN:PaintMenuOption(panel, width, height) if (panel.m_bBackground and (panel.Hovered or panel.Highlight)) then self:DrawImportantBackground(0, 0, width, height, ix.config.Get("color")) end end function SKIN:PaintHelixSlider(panel, width, height) surface.SetDrawColor(self.Colours.DarkerBackground) surface.DrawRect(0, 0, width, height) surface.SetDrawColor(self.Colours.Success) surface.DrawRect(0, 0, panel:GetVisualFraction() * width, height) end function SKIN:PaintChatboxTabButton(panel, width, height) if (panel:GetActive()) then surface.SetDrawColor(ix.config.Get("color", Color(75, 119, 190, 255))) surface.DrawRect(0, 0, width, height) else surface.SetDrawColor(0, 0, 0, 100) surface.DrawRect(0, 0, width, height) if (panel:GetUnread()) then surface.SetDrawColor(ColorAlpha(self.Colours.Warning, Lerp(panel.unreadAlpha, 0, 100))) surface.SetTexture(gradient) surface.DrawTexturedRect(0, 0, width, height - 1) end end -- border surface.SetDrawColor(color_black) surface.DrawRect(width - 1, 0, 1, height) -- right end function SKIN:PaintChatboxTabs(panel, width, height, alpha) surface.SetDrawColor(0, 0, 0, 33) surface.DrawRect(0, 0, width, height) surface.SetDrawColor(0, 0, 0, 100) surface.SetTexture(gradient) surface.DrawTexturedRect(0, height * 0.5, width, height * 0.5) local tab = panel:GetActiveTab() if (tab) then local button = tab:GetButton() local x, _ = button:GetPos() -- outline surface.SetDrawColor(0, 0, 0, 200) surface.DrawRect(0, height - 1, x, 1) -- left surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- right end end function SKIN:PaintChatboxBackground(panel, width, height) ix.util.DrawBlur(panel, 10) if (panel:GetActive()) then surface.SetDrawColor(ColorAlpha(ix.config.Get("color"), 120)) surface.SetTexture(gradientUp) surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25) end surface.SetDrawColor(color_black) surface.DrawOutlinedRect(0, 0, width, height) end function SKIN:PaintChatboxEntry(panel, width, height) surface.SetDrawColor(0, 0, 0, 66) surface.DrawRect(0, 0, width, height) panel:DrawTextEntryText(color_white, ix.config.Get("color"), color_white) surface.SetDrawColor(color_black) surface.DrawOutlinedRect(0, 0, width, height) end function SKIN:DrawChatboxPreviewBox(x, y, text, color) color = color or ix.config.Get("color") local textWidth, textHeight = surface.GetTextSize(text) local width, height = textWidth + 8, textHeight + 8 -- background surface.SetDrawColor(color) surface.DrawRect(x, y, width, height) -- text surface.SetTextColor(color_white) surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) surface.DrawText(text) -- outline surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255) surface.DrawOutlinedRect(x, y, width, height) return width end function SKIN:DrawChatboxPrefixBox(panel, width, height) local color = panel:GetBackgroundColor() -- background surface.SetDrawColor(color) surface.DrawRect(0, 0, width, height) -- outline surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255) surface.DrawOutlinedRect(0, 0, width, height) end function SKIN:PaintChatboxAutocompleteEntry(panel, width, height) -- selected background if (panel.highlightAlpha > 0) then self:DrawImportantBackground(0, 0, width, height, ColorAlpha(ix.config.Get("color"), panel.highlightAlpha * 66)) end -- lower border surface.SetDrawColor(200, 200, 200, 33) surface.DrawRect(0, height - 1, width, 1) end function SKIN:PaintWindowMinimizeButton(panel, width, height) end function SKIN:PaintWindowMaximizeButton(panel, width, height) end function SKIN:PaintInfoBar(panel, width, height, color) -- bar surface.SetDrawColor(color.r, color.g, color.b, 250) surface.DrawRect(0, 0, width, height) -- gradient overlay surface.SetDrawColor(230, 230, 230, 8) surface.SetTexture(gradientUp) surface.DrawTexturedRect(0, 0, width, height) end function SKIN:PaintInfoBarBackground(panel, width, height) surface.SetDrawColor(230, 230, 230, 15) surface.DrawRect(0, 0, width, height) surface.DrawOutlinedRect(0, 0, width, height) panel.bar:PaintManual() DisableClipping(true) panel.label:PaintManual() DisableClipping(false) end function SKIN:PaintInventorySlot(panel, width, height) surface.SetDrawColor(35, 35, 35, 85) surface.DrawRect(1, 1, width - 2, height - 2) surface.SetDrawColor(0, 0, 0, 250) surface.DrawOutlinedRect(1, 1, width - 2, height - 2) end function SKIN:PaintDeathScreenBackground(panel, width, height, progress) surface.SetDrawColor(0, 0, 0, (progress / 0.3) * 255) surface.DrawRect(0, 0, width, height) end function SKIN:PaintDeathScreen(panel, width, height, progress) ix.bar.DrawAction() end do -- check if sounds exist, otherwise fall back to default UI sounds local bWhoosh = file.Exists("sound/helix/ui/whoosh1.wav", "GAME") local bRollover = file.Exists("sound/helix/ui/rollover.wav", "GAME") local bPress = file.Exists("sound/helix/ui/press.wav", "GAME") local bNotify = file.Exists("sound/helix/ui/REPLACEME.wav", "GAME") -- @todo sound.Add({ name = "Helix.Whoosh", channel = CHAN_STATIC, volume = 0.4, level = 80, pitch = bWhoosh and {90, 105} or 100, sound = bWhoosh and { "helix/ui/whoosh1.wav", "helix/ui/whoosh2.wav", "helix/ui/whoosh3.wav", "helix/ui/whoosh4.wav", "helix/ui/whoosh5.wav", "helix/ui/whoosh6.wav" } or "" }) sound.Add({ name = "Helix.Rollover", channel = CHAN_STATIC, volume = 0.5, level = 80, pitch = {95, 105}, sound = bRollover and "helix/ui/rollover.wav" or "ui/buttonrollover.wav" }) sound.Add({ name = "Helix.Press", channel = CHAN_STATIC, volume = 0.5, level = 80, pitch = bPress and {95, 110} or 100, sound = bPress and "helix/ui/press.wav" or "ui/buttonclickrelease.wav" }) sound.Add({ name = "Helix.Notify", channel = CHAN_STATIC, volume = 0.35, level = 80, pitch = 140, sound = bNotify and "helix/ui/REPLACEME.wav" or "weapons/grenade/tick1.wav" }) end derma.RefreshSkins() ================================================ FILE: gamemode/core/derma/cl_attribute.lua ================================================ local gradient = ix.util.GetMaterial("vgui/gradient-u") local gradient2 = ix.util.GetMaterial("vgui/gradient-d") local PANEL = {} AccessorFunc(PANEL, "color", "Color") AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER) AccessorFunc(PANEL, "boostValue", "Boost", FORCE_NUMBER) AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER) function PANEL:Init() self:SetTall(20) self.add = self:Add("DImageButton") self.add:SetSize(16, 16) self.add:Dock(RIGHT) self.add:DockMargin(2, 2, 2, 2) self.add:SetImage("icon16/add.png") self.add.OnMousePressed = function() self.pressing = 1 self:DoChange() self.add:SetAlpha(150) end self.add.OnMouseReleased = function() if (self.pressing) then self.pressing = nil self.add:SetAlpha(255) end end self.add.OnCursorExited = self.add.OnMouseReleased self.sub = self:Add("DImageButton") self.sub:SetSize(16, 16) self.sub:Dock(LEFT) self.sub:DockMargin(2, 2, 2, 2) self.sub:SetImage("icon16/delete.png") self.sub.OnMousePressed = function() self.pressing = -1 self:DoChange() self.sub:SetAlpha(150) end self.sub.OnMouseReleased = function() if (self.pressing) then self.pressing = nil self.sub:SetAlpha(255) end end self.sub.OnCursorExited = self.sub.OnMouseReleased self.value = 0 self.deltaValue = self.value self.max = 10 self.animateSpeed = 15 self.bar = self:Add("DPanel") self.bar:Dock(FILL) self.bar.Paint = function(this, w, h) surface.SetDrawColor(35, 35, 35, 250) surface.DrawRect(0, 0, w, h) w, h = w - 4, h - 4 local value = self.deltaValue / self.max if (value > 0) then local color = self.color and self.color or ix.config.Get("color") local boostedValue = self.boostValue or 0 local add = 0 if (self.deltaValue != self.value) then add = 35 end -- your stat do if !(boostedValue < 0 and math.abs(boostedValue) > self.value) then surface.SetDrawColor(color.r + add, color.g + add, color.b + add, 230) surface.DrawRect(2, 2, w * value, h) surface.SetDrawColor(255, 255, 255, 35) surface.SetMaterial(gradient) surface.DrawTexturedRect(2, 2, w * value, h) end end -- boosted stat do local boostValue if (boostedValue != 0) then if (boostedValue < 0) then local please = math.min(self.value, math.abs(boostedValue)) boostValue = ((please or 0) / self.max) * (self.deltaValue / self.value) else boostValue = ((boostedValue or 0) / self.max) * (self.deltaValue / self.value) end if (boostedValue < 0) then surface.SetDrawColor(200, 40, 40, 230) local bWidth = math.abs(w * boostValue) surface.DrawRect(2 + w * value - bWidth, 2, bWidth, h) surface.SetDrawColor(255, 255, 255, 35) surface.SetMaterial(gradient) surface.DrawTexturedRect(2 + w * value - bWidth, 2, bWidth, h) else surface.SetDrawColor(40, 200, 40, 230) surface.DrawRect(2 + w * value, 2, w * boostValue, h) surface.SetDrawColor(255, 255, 255, 35) surface.SetMaterial(gradient) surface.DrawTexturedRect(2 + w * value, 2, w * boostValue, h) end end end end surface.SetDrawColor(255, 255, 255, 5) surface.SetMaterial(gradient2) surface.DrawTexturedRect(2, 2, w, h) end self.label = self.bar:Add("DLabel") self.label:Dock(FILL) self.label:SetExpensiveShadow(1, Color(0, 0, 60)) self.label:SetContentAlignment(5) end function PANEL:Think() if (self.pressing) then if ((self.nextPress or 0) < CurTime()) then self:DoChange() end end self.deltaValue = math.Approach(self.deltaValue, self.value, FrameTime() * self.animateSpeed) end function PANEL:DoChange() if ((self.value == 0 and self.pressing == -1) or (self.value == self.max and self.pressing == 1)) then return end self.nextPress = CurTime() + 0.2 if (self:OnChanged(self.pressing) != false) then self.value = math.Clamp(self.value + self.pressing, 0, self.max) end end function PANEL:OnChanged(difference) end function PANEL:SetText(text) self.label:SetText(text) end function PANEL:SetReadOnly() self.sub:Remove() self.add:Remove() end vgui.Register("ixAttributeBar", PANEL, "DPanel") ================================================ FILE: gamemode/core/derma/cl_bar.lua ================================================ -- bar manager -- this manages positions for bar panels local PANEL = {} AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) function PANEL:Init() self:SetSize(ScrW() * 0.35, ScrH()) self:SetPos(4, 4) self:ParentToHUD() self.bars = {} self.padding = 2 -- add bars that were registered before manager creation for _, v in ipairs(ix.bar.list) do v.panel = self:AddBar(v.index, v.color, v.priority) end end function PANEL:GetAll() return self.bars end function PANEL:Clear() for k, v in ipairs(self.bars) do v:Remove() table.remove(self.bars, k) end end function PANEL:AddBar(index, color, priority) local panel = self:Add("ixInfoBar") panel:SetSize(self:GetWide(), BAR_HEIGHT) panel:SetVisible(false) panel:SetID(index) panel:SetColor(color) panel:SetPriority(priority) self.bars[#self.bars + 1] = panel self:Sort() return panel end function PANEL:RemoveBar(panel) local toRemove for k, v in ipairs(self.bars) do if (v == panel) then toRemove = k break end end if (toRemove) then table.remove(self.bars, toRemove) -- Decrease index value for the next bars for i = toRemove, #self.bars do ix.bar.list[i].index = i self.bars[i]:SetID(i) end end panel:Remove() self:Sort() end -- sort bars by priority function PANEL:Sort() table.sort(self.bars, function(a, b) return a:GetPriority() < b:GetPriority() end) end -- update target Y positions function PANEL:Organize() local currentY = 0 for _, v in ipairs(self.bars) do if (!v:IsVisible()) then continue end v:SetPos(0, currentY) currentY = currentY + self.padding + v:GetTall() end self:SetSize(self:GetWide(), currentY) end function PANEL:Think() local menu = (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) and ix.gui.characterMenu or IsValid(ix.gui.menu) and ix.gui.menu local fraction = menu and 1 - menu.currentAlpha / 255 or 1 self:SetAlpha(255 * fraction) -- don't update bars when not visible if (fraction == 0) then return end local curTime = CurTime() local bShouldHide = hook.Run("ShouldHideBars") local bAlwaysShow = ix.option.Get("alwaysShowBars", false) for _, v in ipairs(self.bars) do local info = ix.bar.list[v:GetID()] local realValue, barText = info.GetValue() if (bShouldHide or realValue == false) then v:SetVisible(false) continue end if (v:GetDelta() != realValue) then v:SetLifetime(curTime + 5) end if (v:GetLifetime() < curTime and !info.visible and !bAlwaysShow and !hook.Run("ShouldBarDraw", info)) then v:SetVisible(false) continue end v:SetVisible(true) v:SetValue(realValue) v:SetText(isstring(barText) and barText or "") end self:Organize() end function PANEL:OnRemove() self:Clear() end vgui.Register("ixInfoBarManager", PANEL, "Panel") PANEL = {} AccessorFunc(PANEL, "index", "ID", FORCE_NUMBER) AccessorFunc(PANEL, "color", "Color") AccessorFunc(PANEL, "priority", "Priority", FORCE_NUMBER) AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER) AccessorFunc(PANEL, "delta", "Delta", FORCE_NUMBER) AccessorFunc(PANEL, "lifetime", "Lifetime", FORCE_NUMBER) function PANEL:Init() self.value = 0 self.delta = 0 self.lifetime = 0 self.bar = self:Add("DPanel") self.bar:SetPaintedManually(true) self.bar:Dock(FILL) self.bar:DockMargin(2, 2, 2, 2) self.bar.Paint = function(this, width, height) width = width * math.min(self.delta, 1) derma.SkinFunc("PaintInfoBar", self, width, height, self.color) end self.label = self:Add("DLabel") self.label:SetFont("ixSmallFont") self.label:SetContentAlignment(5) self.label:SetText("") self.label:SetTextColor(Color(240, 240, 240)) self.label:SetExpensiveShadow(2, Color(20, 20, 20)) self.label:SetPaintedManually(true) self.label:SizeToContents() self.label:Dock(FILL) end function PANEL:SetText(text) self.label:SetText(text) self.label:SizeToContents() end function PANEL:Think() self.delta = math.Approach(self.delta, self.value, FrameTime()) end function PANEL:Paint(width, height) derma.SkinFunc("PaintInfoBarBackground", self, width, height) end vgui.Register("ixInfoBar", PANEL, "Panel") if (IsValid(ix.gui.bars)) then ix.gui.bars:Remove() ix.gui.bars = vgui.Create("ixInfoBarManager") end ================================================ FILE: gamemode/core/derma/cl_business.lua ================================================ local PANEL = {} function PANEL:Init() -- being relative. local size = 120 self:SetSize(size, size * 1.4) end function PANEL:SetItem(itemTable) self.itemName = L(itemTable.name):lower() self.price = self:Add("DLabel") self.price:Dock(BOTTOM) self.price:SetText(itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper()) self.price:SetContentAlignment(5) self.price:SetTextColor(color_white) self.price:SetFont("ixSmallFont") self.price:SetExpensiveShadow(1, Color(0, 0, 0, 200)) self.name = self:Add("DLabel") self.name:Dock(TOP) self.name:SetText(itemTable.GetName and itemTable:GetName() or L(itemTable.name)) self.name:SetContentAlignment(5) self.name:SetTextColor(color_white) self.name:SetFont("ixSmallFont") self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200)) self.name.Paint = function(this, w, h) surface.SetDrawColor(0, 0, 0, 75) surface.DrawRect(0, 0, w, h) end self.icon = self:Add("SpawnIcon") self.icon:SetZPos(1) self.icon:SetSize(self:GetWide(), self:GetWide()) self.icon:Dock(FILL) self.icon:DockMargin(5, 5, 5, 10) self.icon:InvalidateLayout(true) self.icon:SetModel(itemTable:GetModel(), itemTable:GetSkin()) self.icon:SetHelixTooltip(function(tooltip) ix.hud.PopulateItemTooltip(tooltip, itemTable) end) self.icon.itemTable = itemTable self.icon.DoClick = function(this) if (IsValid(ix.gui.checkout)) then return end local parent = ix.gui.business local bAdded = parent:BuyItem(itemTable.uniqueID) if (bAdded) then surface.PlaySound("buttons/button14.wav") end end self.icon.PaintOver = function(this) if (itemTable and itemTable.PaintOver) then local w, h = this:GetSize() itemTable.PaintOver(this, itemTable, w, h) end end if ((itemTable.iconCam and !ICON_RENDER_QUEUE[itemTable.uniqueID]) or itemTable.forceRender) then local iconCam = itemTable.iconCam iconCam = { cam_pos = iconCam.pos, cam_fov = iconCam.fov, cam_ang = iconCam.ang, } ICON_RENDER_QUEUE[itemTable.uniqueID] = true self.icon:RebuildSpawnIconEx( iconCam ) end end vgui.Register("ixBusinessItem", PANEL, "DPanel") PANEL = {} function PANEL:Init() ix.gui.business = self self:SetSize(self:GetParent():GetSize()) self.categories = self:Add("DScrollPanel") self.categories:Dock(LEFT) self.categories:SetWide(260) self.categories.Paint = function(this, w, h) surface.SetDrawColor(0, 0, 0, 150) surface.DrawRect(0, 0, w, h) end self.categories:DockPadding(5, 5, 5, 5) self.categories:DockMargin(0, 46, 0, 0) self.categoryPanels = {} self.scroll = self:Add("DScrollPanel") self.scroll:Dock(FILL) self.search = self:Add("DTextEntry") self.search:Dock(TOP) self.search:SetTall(36) self.search:SetFont("ixMediumFont") self.search:DockMargin(10 + self:GetWide() * 0.35, 0, 5, 5) self.search.OnTextChanged = function(this) local text = self.search:GetText():lower() if (self.selected) then self:LoadItems(self.selected.category, text:find("%S") and text or nil) self.scroll:InvalidateLayout() end end self.search.PaintOver = function(this, cw, ch) if (self.search:GetValue() == "" and !self.search:HasFocus()) then ix.util.DrawText("V", 10, ch/2 - 1, color_black, 3, 1, "ixIconsSmall") end end self.itemList = self.scroll:Add("DIconLayout") self.itemList:Dock(TOP) self.itemList:DockMargin(10, 1, 5, 5) self.itemList:SetSpaceX(10) self.itemList:SetSpaceY(10) self.itemList:SetMinimumSize(128, 400) self.checkout = self:Add("DButton") self.checkout:Dock(BOTTOM) self.checkout:SetTextColor(color_white) self.checkout:SetTall(36) self.checkout:SetFont("ixMediumFont") self.checkout:DockMargin(10, 10, 0, 0) self.checkout:SetExpensiveShadow(1, Color(0, 0, 0, 150)) self.checkout:SetText(L("checkout", 0)) self.checkout.DoClick = function() if (!IsValid(ix.gui.checkout) and self:GetCartCount() > 0) then vgui.Create("ixBusinessCheckout"):SetCart(self.cart) end end self.cart = {} local dark = Color(0, 0, 0, 50) local first = true for k, v in pairs(ix.item.list) do if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), k) == false) then continue end if (!self.categoryPanels[L(v.category)]) then self.categoryPanels[L(v.category)] = v.category end end for category, realName in SortedPairs(self.categoryPanels) do local button = self.categories:Add("DButton") button:SetTall(36) button:SetText(category) button:Dock(TOP) button:SetTextColor(color_white) button:DockMargin(5, 5, 5, 0) button:SetFont("ixMediumFont") button:SetExpensiveShadow(1, Color(0, 0, 0, 150)) button.Paint = function(this, w, h) surface.SetDrawColor(self.selected == this and ix.config.Get("color") or dark) surface.DrawRect(0, 0, w, h) surface.SetDrawColor(0, 0, 0, 50) surface.DrawOutlinedRect(0, 0, w, h) end button.DoClick = function(this) if (self.selected != this) then self.selected = this self:LoadItems(realName) timer.Simple(0.01, function() self.scroll:InvalidateLayout() end) end end button.category = realName if (first) then self.selected = button first = false end self.categoryPanels[realName] = button end if (self.selected) then self:LoadItems(self.selected.category) end end function PANEL:GetCartCount() local count = 0 for _, v in pairs(self.cart) do count = count + v end return count end function PANEL:BuyItem(uniqueID) local currentCount = self.cart[uniqueID] or 0 if (currentCount >= 10) then return false end self.cart[uniqueID] = currentCount + 1 self.checkout:SetText(L("checkout", self:GetCartCount())) return true end function PANEL:LoadItems(category, search) category = category or "misc" local items = ix.item.list self.itemList:Clear() self.itemList:InvalidateLayout(true) for uniqueID, itemTable in SortedPairsByMemberValue(items, "name") do if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), uniqueID) == false) then continue end if (itemTable.category == category) then if (search and search != "" and !L(itemTable.name):lower():find(search, 1, true)) then continue end self.itemList:Add("ixBusinessItem"):SetItem(itemTable) end end end function PANEL:SetPage() end function PANEL:GetPageItems() end vgui.Register("ixBusiness", PANEL, "EditablePanel") DEFINE_BASECLASS("DFrame") PANEL = {} function PANEL:Init() if (IsValid(ix.gui.checkout)) then ix.gui.checkout:Remove() end ix.gui.checkout = self self:SetTitle(L("checkout", 0)) self:SetSize(ScrW() / 4 > 200 and ScrW() / 4 or ScrW() / 2, ScrH() / 2 > 300 and ScrH() / 2 or ScrH()) self:MakePopup() self:Center() self:SetBackgroundBlur(true) self:SetSizable(true) self.items = self:Add("DScrollPanel") self.items:Dock(FILL) self.items.Paint = function(this, w, h) surface.SetDrawColor(0, 0, 0, 100) surface.DrawRect(0, 0, w, h) end self.items:DockMargin(0, 0, 0, 4) self.buy = self:Add("DButton") self.buy:Dock(BOTTOM) self.buy:SetText(L"purchase") self.buy:SetTextColor(color_white) self.buy.DoClick = function(this) if ((this.nextClick or 0) < CurTime()) then this.nextClick = CurTime() + 0.5 else return end if (self.preventBuy) then self.finalGlow:SetText(self.final:GetText()) self.finalGlow:SetAlpha(255) self.finalGlow:AlphaTo(0, 0.5) return surface.PlaySound("buttons/button11.wav") end net.Start("ixBusinessBuy") net.WriteUInt(table.Count(self.itemData), 8) for k, v in pairs(self.itemData) do net.WriteString(k) net.WriteUInt(v, 8) end net.SendToServer() self.itemData = {} self.items:Remove() self.data:Remove() self.buy:Remove() self:ShowCloseButton(false) if (IsValid(ix.gui.business)) then ix.gui.business.cart = {} ix.gui.business.checkout:SetText(L("checkout", 0)) end self.text = self:Add("DLabel") self.text:Dock(FILL) self.text:SetContentAlignment(5) self.text:SetTextColor(color_white) self.text:SetText(L"purchasing") self.text:SetFont("ixMediumFont") net.Receive("ixBusinessResponse", function() if (IsValid(self)) then self.text:SetText(L"buyGood") self.done = true self:ShowCloseButton(true) surface.PlaySound("buttons/button3.wav") end end) timer.Simple(4, function() if (IsValid(self) and !self.done) then self.text:SetText(L"buyFailed") self:ShowCloseButton(true) surface.PlaySound("buttons/button11.wav") end end) end self.data = self:Add("DPanel") self.data:Dock(BOTTOM) self.data:SetTall(64) self.data:DockMargin(0, 0, 0, 4) self.current = self.data:Add("DLabel") self.current:SetFont("ixSmallFont") self.current:SetContentAlignment(6) self.current:SetTextColor(color_white) self.current:Dock(TOP) self.current:SetTextInset(4, 0) self.total = self.data:Add("DLabel") self.total:SetFont("ixSmallFont") self.total:SetContentAlignment(6) self.total:SetTextColor(color_white) self.total:Dock(TOP) self.total:SetTextInset(4, 0) local line = self.data:Add("DPanel") line:SetTall(1) line:DockMargin(128, 0, 4, 0) line:Dock(TOP) line.Paint = function(this, w, h) surface.SetDrawColor(255, 255, 255, 150) surface.DrawLine(0, 0, w, 0) end self.final = self.data:Add("DLabel") self.final:SetFont("ixSmallFont") self.final:SetContentAlignment(6) self.final:SetTextColor(color_white) self.final:Dock(TOP) self.final:SetTextInset(4, 0) self.finalGlow = self.final:Add("DLabel") self.finalGlow:Dock(FILL) self.finalGlow:SetFont("ixSmallFont") self.finalGlow:SetTextColor(color_white) self.finalGlow:SetContentAlignment(6) self.finalGlow:SetAlpha(0) self.finalGlow:SetTextInset(4, 0) self:SetFocusTopLevel(true) self.itemData = {} self:OnQuantityChanged() end function PANEL:OnQuantityChanged() local price = 0 local money = LocalPlayer():GetCharacter():GetMoney() local valid = 0 for k, v in pairs(self.itemData) do local itemTable = ix.item.list[k] if (itemTable and v > 0) then valid = valid + 1 price = price + (v * (itemTable.price or 0)) end end self.current:SetText(L"currentMoney" .. ix.currency.Get(money)) self.total:SetText("- " .. ix.currency.Get(price)) self.final:SetText(L"moneyLeft" .. ix.currency.Get(money - price)) self.final:SetTextColor((money - price) >= 0 and Color(46, 204, 113) or Color(217, 30, 24)) self.preventBuy = (money - price) < 0 or valid == 0 if (IsValid(ix.gui.business)) then ix.gui.business.checkout:SetText(L("checkout", ix.gui.business:GetCartCount())) end end function PANEL:SetCart(items) self.itemData = items for k, v in SortedPairs(items) do local itemTable = ix.item.list[k] if (itemTable) then local slot = self.items:Add("DPanel") slot:SetTall(36) slot:Dock(TOP) slot:DockMargin(5, 5, 5, 0) slot.icon = slot:Add("SpawnIcon") slot.icon:SetPos(2, 2) slot.icon:SetSize(32, 32) slot.icon:SetModel(itemTable:GetModel()) slot.icon:SetTooltip() slot.name = slot:Add("DLabel") slot.name:SetPos(40, 2) slot.name:SetFont("ixChatFont") slot.name:SetText(string.format( "%s (%s)", L(itemTable.GetName and itemTable:GetName() or L(itemTable.name)), itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper() )) slot.name:SetTextColor(color_white) slot.name:SizeToContents() slot.name:DockMargin(40, 0, 0, 0) slot.name:Dock(FILL) slot.quantity = slot:Add("DTextEntry") slot.quantity:SetSize(32, 32) slot.quantity:Dock(RIGHT) slot.quantity:DockMargin(4, 4, 4, 4) slot.quantity:SetContentAlignment(5) slot.quantity:SetNumeric(true) slot.quantity:SetText(v) slot.quantity:SetFont("ixChatFont") slot.quantity.OnTextChanged = function(this) local value = tonumber(this:GetValue()) if (!value) then this:SetValue(1) return end value = math.Clamp(math.Round(value), 0, 10) if (value == 0) then items[k] = nil slot:Remove() else items[k] = value end self:OnQuantityChanged() end slot.quantity.OnLoseFocus = function(this) local value = math.Clamp(tonumber(this:GetValue()) or 1, 0, 10) this:SetText(value) end else items[k] = nil end end self:OnQuantityChanged() end function PANEL:Think() if (!self:HasFocus()) then self:MakePopup() end BaseClass.Think(self) end vgui.Register("ixBusinessCheckout", PANEL, "DFrame") hook.Add("CreateMenuButtons", "ixBusiness", function(tabs) if (hook.Run("BuildBusinessMenu") != false) then tabs["business"] = function(container) container:Add("ixBusiness") end end end) ================================================ FILE: gamemode/core/derma/cl_character.lua ================================================ local gradient = surface.GetTextureID("vgui/gradient-d") local audioFadeInTime = 2 local animationTime = 0.5 local matrixZScale = Vector(1, 1, 0.0001) -- character menu panel DEFINE_BASECLASS("ixSubpanelParent") local PANEL = {} function PANEL:Init() self:SetSize(self:GetParent():GetSize()) self:SetPos(0, 0) self.childPanels = {} self.subpanels = {} self.activeSubpanel = "" self.currentDimAmount = 0 self.currentY = 0 self.currentScale = 1 self.currentAlpha = 255 self.targetDimAmount = 255 self.targetScale = 0.9 end function PANEL:Dim(length, callback) length = length or animationTime self.currentDimAmount = 0 self:CreateAnimation(length, { target = { currentDimAmount = self.targetDimAmount, currentScale = self.targetScale }, easing = "outCubic", OnComplete = callback }) self:OnDim() end function PANEL:Undim(length, callback) length = length or animationTime self.currentDimAmount = self.targetDimAmount self:CreateAnimation(length, { target = { currentDimAmount = 0, currentScale = 1 }, easing = "outCubic", OnComplete = callback }) self:OnUndim() end function PANEL:OnDim() end function PANEL:OnUndim() end function PANEL:Paint(width, height) local amount = self.currentDimAmount local bShouldScale = self.currentScale != 1 local matrix -- draw child panels with scaling if needed if (bShouldScale) then matrix = Matrix() matrix:Scale(matrixZScale * self.currentScale) matrix:Translate(Vector( ScrW() * 0.5 - (ScrW() * self.currentScale * 0.5), ScrH() * 0.5 - (ScrH() * self.currentScale * 0.5), 1 )) cam.PushModelMatrix(matrix) self.currentMatrix = matrix end BaseClass.Paint(self, width, height) if (bShouldScale) then cam.PopModelMatrix() self.currentMatrix = nil end if (amount > 0) then local color = Color(0, 0, 0, amount) surface.SetDrawColor(color) surface.DrawRect(0, 0, width, height) end end vgui.Register("ixCharMenuPanel", PANEL, "ixSubpanelParent") -- character menu main button list PANEL = {} function PANEL:Init() local parent = self:GetParent() self:SetSize(parent:GetWide() * 0.25, parent:GetTall()) self:GetVBar():SetWide(0) self:GetVBar():SetVisible(false) end function PANEL:Add(name) local panel = vgui.Create(name, self) panel:Dock(TOP) return panel end function PANEL:SizeToContents() self:GetCanvas():InvalidateLayout(true) -- if the canvas has extra space, forcefully dock to the bottom so it doesn't anchor to the top if (self:GetTall() > self:GetCanvas():GetTall()) then self:GetCanvas():Dock(BOTTOM) else self:GetCanvas():Dock(NODOCK) end end vgui.Register("ixCharMenuButtonList", PANEL, "DScrollPanel") -- main character menu panel PANEL = {} AccessorFunc(PANEL, "bUsingCharacter", "UsingCharacter", FORCE_BOOL) function PANEL:Init() local parent = self:GetParent() local padding = self:GetPadding() local halfWidth = ScrW() * 0.5 local halfPadding = padding * 0.5 local bHasCharacter = #ix.characters > 0 self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() self:DockPadding(padding, padding, padding, padding) local infoLabel = self:Add("DLabel") infoLabel:SetTextColor(Color(255, 255, 255, 25)) infoLabel:SetFont("ixMenuMiniFont") infoLabel:SetText(L("helix") .. " " .. GAMEMODE.Version) infoLabel:SizeToContents() infoLabel:SetPos(ScrW() - infoLabel:GetWide() - 4, ScrH() - infoLabel:GetTall() - 4) local logoPanel = self:Add("Panel") logoPanel:SetSize(ScrW(), ScrH() * 0.25) logoPanel:SetPos(0, ScrH() * 0.25) logoPanel.Paint = function(panel, width, height) local matrix = self.currentMatrix -- don't scale the background because it fucks the blur if (matrix) then cam.PopModelMatrix() end local newHeight = Lerp(1 - (self.currentDimAmount / 255), 0, height) local y = height * 0.5 - newHeight * 0.5 local _, screenY = panel:LocalToScreen(0, 0) screenY = screenY + y render.SetScissorRect(0, screenY, width, screenY + newHeight, true) ix.util.DrawBlur(panel, 15, nil, 200) -- background dim surface.SetDrawColor(0, 0, 0, 100) surface.DrawRect(0, y, width, newHeight) -- border lines surface.SetDrawColor(ix.config.Get("color") or color_white) surface.DrawRect(0, y, width, 1) surface.DrawRect(0, y + newHeight - 1, width, 1) if (matrix) then cam.PushModelMatrix(matrix) end for _, v in ipairs(panel:GetChildren()) do v:PaintManual() end render.SetScissorRect(0, 0, 0, 0, false) end -- draw schema logo material instead of text if available local logo = Schema.logo and ix.util.GetMaterial(Schema.logo) if (logo and !logo:IsError()) then local logoImage = logoPanel:Add("DImage") logoImage:SetMaterial(logo) logoImage:SetSize(halfWidth, halfWidth * logo:Height() / logo:Width()) logoImage:SetPos(halfWidth - logoImage:GetWide() * 0.5, halfPadding) logoImage:SetPaintedManually(true) logoPanel:SetTall(logoImage:GetTall() + padding) else local newHeight = padding local subtitle = L2("schemaDesc") or Schema.description local titleLabel = logoPanel:Add("DLabel") titleLabel:SetTextColor(color_white) titleLabel:SetFont("ixTitleFont") titleLabel:SetText(L2("schemaName") or Schema.name or L"unknown") titleLabel:SizeToContents() titleLabel:SetPos(halfWidth - titleLabel:GetWide() * 0.5, halfPadding) titleLabel:SetPaintedManually(true) newHeight = newHeight + titleLabel:GetTall() if (subtitle) then local subtitleLabel = logoPanel:Add("DLabel") subtitleLabel:SetTextColor(color_white) subtitleLabel:SetFont("ixSubTitleFont") subtitleLabel:SetText(subtitle) subtitleLabel:SizeToContents() subtitleLabel:SetPos(halfWidth - subtitleLabel:GetWide() * 0.5, 0) subtitleLabel:MoveBelow(titleLabel) subtitleLabel:SetPaintedManually(true) newHeight = newHeight + subtitleLabel:GetTall() end logoPanel:SetTall(newHeight) end -- button list self.mainButtonList = self:Add("ixCharMenuButtonList") self.mainButtonList:Dock(LEFT) -- create character button local createButton = self.mainButtonList:Add("ixMenuButton") createButton:SetText("create") createButton:SizeToContents() createButton.DoClick = function() local maximum = hook.Run("GetMaxPlayerCharacter", LocalPlayer()) or ix.config.Get("maxCharacters", 5) -- don't allow creation if we've hit the character limit if (#ix.characters >= maximum) then self:GetParent():ShowNotice(3, L("maxCharacters")) return end self:Dim() parent.newCharacterPanel:SetActiveSubpanel("faction", 0) parent.newCharacterPanel:SlideUp() end -- load character button self.loadButton = self.mainButtonList:Add("ixMenuButton") self.loadButton:SetText("load") self.loadButton:SizeToContents() self.loadButton.DoClick = function() self:Dim() parent.loadCharacterPanel:SlideUp() end if (!bHasCharacter) then self.loadButton:SetDisabled(true) end -- community button local extraURL = ix.config.Get("communityURL", "") local extraText = ix.config.Get("communityText", "@community") if (extraURL != "" and extraText != "") then if (extraText:sub(1, 1) == "@") then extraText = L(extraText:sub(2)) end local extraButton = self.mainButtonList:Add("ixMenuButton") extraButton:SetText(extraText, true) extraButton:SizeToContents() extraButton.DoClick = function() gui.OpenURL(extraURL) end end -- leave/return button self.returnButton = self.mainButtonList:Add("ixMenuButton") self:UpdateReturnButton() self.returnButton.DoClick = function() if (self.bUsingCharacter) then parent:Close() else RunConsoleCommand("disconnect") end end self.mainButtonList:SizeToContents() end function PANEL:UpdateReturnButton(bValue) if (bValue != nil) then self.bUsingCharacter = bValue end self.returnButton:SetText(self.bUsingCharacter and "return" or "leave") self.returnButton:SizeToContents() end function PANEL:OnDim() -- disable input on this panel since it will still be in the background while invisible - prone to stray clicks if the -- panels overtop slide out of the way self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) end function PANEL:OnUndim() self:SetMouseInputEnabled(true) self:SetKeyboardInputEnabled(true) -- we may have just deleted a character so update the status of the return button self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() self:UpdateReturnButton() end function PANEL:OnClose() for _, v in pairs(self:GetChildren()) do if (IsValid(v)) then v:SetVisible(false) end end end function PANEL:PerformLayout(width, height) local padding = self:GetPadding() self.mainButtonList:SetPos(padding, height - self.mainButtonList:GetTall() - padding) end vgui.Register("ixCharMenuMain", PANEL, "ixCharMenuPanel") -- container panel PANEL = {} function PANEL:Init() if (IsValid(ix.gui.loading)) then ix.gui.loading:Remove() end if (IsValid(ix.gui.characterMenu)) then if (IsValid(ix.gui.characterMenu.channel)) then ix.gui.characterMenu.channel:Stop() end ix.gui.characterMenu:Remove() end self:SetSize(ScrW(), ScrH()) self:SetPos(0, 0) -- main menu panel self.mainPanel = self:Add("ixCharMenuMain") -- new character panel self.newCharacterPanel = self:Add("ixCharMenuNew") self.newCharacterPanel:SlideDown(0) -- load character panel self.loadCharacterPanel = self:Add("ixCharMenuLoad") self.loadCharacterPanel:SlideDown(0) -- notice bar self.notice = self:Add("ixNoticeBar") -- finalization self:MakePopup() self.currentAlpha = 255 self.volume = 0 ix.gui.characterMenu = self if (!IsValid(ix.gui.intro)) then self:PlayMusic() end hook.Run("OnCharacterMenuCreated", self) end function PANEL:PlayMusic() local path = "sound/" .. ix.config.Get("music") local url = path:match("http[s]?://.+") local play = url and sound.PlayURL or sound.PlayFile path = url and url or path play(path, "noplay", function(channel, error, message) if (!IsValid(self) or !IsValid(channel)) then return end channel:SetVolume(self.volume or 0) channel:Play() self.channel = channel self:CreateAnimation(audioFadeInTime, { index = 10, target = {volume = 1}, Think = function(animation, panel) if (IsValid(panel.channel)) then panel.channel:SetVolume(self.volume * 0.5) end end }) end) end function PANEL:ShowNotice(type, text) self.notice:SetType(type) self.notice:SetText(text) self.notice:Show() end function PANEL:HideNotice() if (IsValid(self.notice) and !self.notice:GetHidden()) then self.notice:Slide("up", 0.5, true) end end function PANEL:OnCharacterDeleted(character) if (#ix.characters == 0) then self.mainPanel.loadButton:SetDisabled(true) self.mainPanel:Undim() -- undim since the load panel will slide down else self.mainPanel.loadButton:SetDisabled(false) end self.loadCharacterPanel:OnCharacterDeleted(character) end function PANEL:OnCharacterLoadFailed(error) self.loadCharacterPanel:SetMouseInputEnabled(true) self.loadCharacterPanel:SlideUp() self:ShowNotice(3, error) end function PANEL:IsClosing() return self.bClosing end function PANEL:Close(bFromMenu) self.bClosing = true self.bFromMenu = bFromMenu local fadeOutTime = animationTime * 8 self:CreateAnimation(fadeOutTime, { index = 1, target = {currentAlpha = 0}, Think = function(animation, panel) panel:SetAlpha(panel.currentAlpha) end, OnComplete = function(animation, panel) panel:Remove() end }) self:CreateAnimation(fadeOutTime - 0.1, { index = 10, target = {volume = 0}, Think = function(animation, panel) if (IsValid(panel.channel)) then panel.channel:SetVolume(self.volume * 0.5) end end, OnComplete = function(animation, panel) if (IsValid(panel.channel)) then panel.channel:Stop() panel.channel = nil end end }) -- hide children if we're already dimmed if (bFromMenu) then for _, v in pairs(self:GetChildren()) do if (IsValid(v)) then v:SetVisible(false) end end else -- fade out the main panel quicker because it significantly blocks the screen self.mainPanel.currentAlpha = 255 self.mainPanel:CreateAnimation(animationTime * 2, { target = {currentAlpha = 0}, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.currentAlpha) end, OnComplete = function(animation, panel) panel:SetVisible(false) end }) end -- relinquish mouse control self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) gui.EnableScreenClicker(false) end function PANEL:Paint(width, height) surface.SetTexture(gradient) surface.SetDrawColor(0, 0, 0, 255) surface.DrawTexturedRect(0, 0, width, height) if (!ix.option.Get("cheapBlur", false)) then surface.SetDrawColor(0, 0, 0, 150) surface.DrawTexturedRect(0, 0, width, height) ix.util.DrawBlur(self, Lerp((self.currentAlpha - 200) / 255, 0, 10)) end end function PANEL:PaintOver(width, height) if (self.bClosing and self.bFromMenu) then surface.SetDrawColor(color_black) surface.DrawRect(0, 0, width, height) end end function PANEL:OnRemove() if (IsValid(self.channel)) then self.channel:Stop() self.channel = nil end end vgui.Register("ixCharMenu", PANEL, "EditablePanel") if (IsValid(ix.gui.characterMenu)) then ix.gui.characterMenu:Remove() --TODO: REMOVE ME ix.gui.characterMenu = vgui.Create("ixCharMenu") end ================================================ FILE: gamemode/core/derma/cl_charcreate.lua ================================================ local padding = ScreenScale(32) -- create character panel DEFINE_BASECLASS("ixCharMenuPanel") local PANEL = {} function PANEL:Init() local parent = self:GetParent() local halfWidth = parent:GetWide() * 0.5 - (padding * 2) local halfHeight = parent:GetTall() * 0.5 - (padding * 2) local modelFOV = (ScrW() > ScrH() * 1.8) and 100 or 78 self:ResetPayload(true) self.factionButtons = {} self.repopulatePanels = {} -- faction selection subpanel self.factionPanel = self:AddSubpanel("faction", true) self.factionPanel:SetTitle("chooseFaction") self.factionPanel.OnSetActive = function() -- if we only have one faction, we are always selecting that one so we can skip to the description section if (#self.factionButtons == 1) then self:SetActiveSubpanel("description", 0) end end local modelList = self.factionPanel:Add("Panel") modelList:Dock(RIGHT) modelList:SetSize(halfWidth + padding * 2, halfHeight) local proceed = modelList:Add("ixMenuButton") proceed:SetText("proceed") proceed:SetContentAlignment(6) proceed:Dock(BOTTOM) proceed:SizeToContents() proceed.DoClick = function() self.progress:IncrementProgress() self:Populate() self:SetActiveSubpanel("description") end self.factionModel = modelList:Add("ixModelPanel") self.factionModel:Dock(FILL) self.factionModel:SetModel("models/error.mdl") self.factionModel:SetFOV(modelFOV) self.factionModel.PaintModel = self.factionModel.Paint self.factionButtonsPanel = self.factionPanel:Add("ixCharMenuButtonList") self.factionButtonsPanel:SetWide(halfWidth) self.factionButtonsPanel:Dock(FILL) local factionBack = self.factionPanel:Add("ixMenuButton") factionBack:SetText("return") factionBack:SizeToContents() factionBack:Dock(BOTTOM) factionBack.DoClick = function() self.progress:DecrementProgress() self:SetActiveSubpanel("faction", 0) self:SlideDown() parent.mainPanel:Undim() end -- character customization subpanel self.description = self:AddSubpanel("description") self.description:SetTitle("chooseDescription") local descriptionModelList = self.description:Add("Panel") descriptionModelList:Dock(LEFT) descriptionModelList:SetSize(halfWidth, halfHeight) local descriptionBack = descriptionModelList:Add("ixMenuButton") descriptionBack:SetText("return") descriptionBack:SetContentAlignment(4) descriptionBack:SizeToContents() descriptionBack:Dock(BOTTOM) descriptionBack.DoClick = function() self.progress:DecrementProgress() if (#self.factionButtons == 1) then factionBack:DoClick() else self:SetActiveSubpanel("faction") end end self.descriptionModel = descriptionModelList:Add("ixModelPanel") self.descriptionModel:Dock(FILL) self.descriptionModel:SetModel(self.factionModel:GetModel()) self.descriptionModel:SetFOV(modelFOV - 13) self.descriptionModel.PaintModel = self.descriptionModel.Paint self.descriptionPanel = self.description:Add("Panel") self.descriptionPanel:SetWide(halfWidth + padding * 2) self.descriptionPanel:Dock(RIGHT) local descriptionProceed = self.descriptionPanel:Add("ixMenuButton") descriptionProceed:SetText("proceed") descriptionProceed:SetContentAlignment(6) descriptionProceed:SizeToContents() descriptionProceed:Dock(BOTTOM) descriptionProceed.DoClick = function() if (self:VerifyProgression("description")) then -- there are no panels on the attributes section other than the create button, so we can just create the character if (#self.attributesPanel:GetChildren() < 2) then self:SendPayload() return end self.progress:IncrementProgress() self:SetActiveSubpanel("attributes") end end -- attributes subpanel self.attributes = self:AddSubpanel("attributes") self.attributes:SetTitle("chooseSkills") local attributesModelList = self.attributes:Add("Panel") attributesModelList:Dock(LEFT) attributesModelList:SetSize(halfWidth, halfHeight) local attributesBack = attributesModelList:Add("ixMenuButton") attributesBack:SetText("return") attributesBack:SetContentAlignment(4) attributesBack:SizeToContents() attributesBack:Dock(BOTTOM) attributesBack.DoClick = function() self.progress:DecrementProgress() self:SetActiveSubpanel("description") end self.attributesModel = attributesModelList:Add("ixModelPanel") self.attributesModel:Dock(FILL) self.attributesModel:SetModel(self.factionModel:GetModel()) self.attributesModel:SetFOV(modelFOV - 13) self.attributesModel.PaintModel = self.attributesModel.Paint self.attributesPanel = self.attributes:Add("Panel") self.attributesPanel:SetWide(halfWidth + padding * 2) self.attributesPanel:Dock(RIGHT) local create = self.attributesPanel:Add("ixMenuButton") create:SetText("finish") create:SetContentAlignment(6) create:SizeToContents() create:Dock(BOTTOM) create.DoClick = function() self:SendPayload() end -- creation progress panel self.progress = self:Add("ixSegmentedProgress") self.progress:SetBarColor(ix.config.Get("color")) self.progress:SetSize(parent:GetWide(), 0) self.progress:SizeToContents() self.progress:SetPos(0, parent:GetTall() - self.progress:GetTall()) -- setup payload hooks self:AddPayloadHook("model", function(value) local faction = ix.faction.indices[self.payload.faction] if (faction) then local model = faction:GetModels(LocalPlayer())[value] -- assuming bodygroups if (istable(model)) then self.factionModel:SetModel(model[1], model[2] or 0, model[3]) self.descriptionModel:SetModel(model[1], model[2] or 0, model[3]) self.attributesModel:SetModel(model[1], model[2] or 0, model[3]) else self.factionModel:SetModel(model) self.descriptionModel:SetModel(model) self.attributesModel:SetModel(model) end end end) -- setup character creation hooks net.Receive("ixCharacterAuthed", function() timer.Remove("ixCharacterCreateTimeout") self.awaitingResponse = false local id = net.ReadUInt(32) local indices = net.ReadUInt(6) local charList = {} for _ = 1, indices do charList[#charList + 1] = net.ReadUInt(32) end ix.characters = charList self:SlideDown() if (!IsValid(self) or !IsValid(parent)) then return end if (LocalPlayer():GetCharacter()) then parent.mainPanel:Undim() parent:ShowNotice(2, L("charCreated")) elseif (id) then self.bMenuShouldClose = true net.Start("ixCharacterChoose") net.WriteUInt(id, 32) net.SendToServer() else self:SlideDown() end end) net.Receive("ixCharacterAuthFailed", function() timer.Remove("ixCharacterCreateTimeout") self.awaitingResponse = false local fault = net.ReadString() local args = net.ReadTable() self:SlideDown() parent.mainPanel:Undim() parent:ShowNotice(3, L(fault, unpack(args))) end) end function PANEL:SendPayload() if (self.awaitingResponse or !self:VerifyProgression()) then return end self.awaitingResponse = true timer.Create("ixCharacterCreateTimeout", 10, 1, function() if (IsValid(self) and self.awaitingResponse) then local parent = self:GetParent() self.awaitingResponse = false self:SlideDown() parent.mainPanel:Undim() parent:ShowNotice(3, L("unknownError")) end end) self.payload:Prepare() net.Start("ixCharacterCreate") net.WriteUInt(table.Count(self.payload), 8) for k, v in pairs(self.payload) do net.WriteString(k) net.WriteType(v) end net.SendToServer() end function PANEL:OnSlideUp() self:ResetPayload() self:Populate() self.progress:SetProgress(1) -- the faction subpanel will skip to next subpanel if there is only one faction to choose from, -- so we don't have to worry about it here self:SetActiveSubpanel("faction", 0) end function PANEL:OnSlideDown() end function PANEL:ResetPayload(bWithHooks) if (bWithHooks) then self.hooks = {} end self.payload = {} -- TODO: eh.. function self.payload.Set(payload, key, value) self:SetPayload(key, value) end function self.payload.AddHook(payload, key, callback) self:AddPayloadHook(key, callback) end function self.payload.Prepare(payload) self.payload.Set = nil self.payload.AddHook = nil self.payload.Prepare = nil end end function PANEL:SetPayload(key, value) self.payload[key] = value self:RunPayloadHook(key, value) end function PANEL:AddPayloadHook(key, callback) if (!self.hooks[key]) then self.hooks[key] = {} end self.hooks[key][#self.hooks[key] + 1] = callback end function PANEL:RunPayloadHook(key, value) local hooks = self.hooks[key] or {} for _, v in ipairs(hooks) do v(value) end end function PANEL:GetContainerPanel(name) -- TODO: yuck if (name == "description") then return self.descriptionPanel elseif (name == "attributes") then return self.attributesPanel end return self.descriptionPanel end function PANEL:AttachCleanup(panel) self.repopulatePanels[#self.repopulatePanels + 1] = panel end function PANEL:Populate() if (!self.bInitialPopulate) then -- setup buttons for the faction panel -- TODO: make this a bit less janky local lastSelected for _, v in pairs(self.factionButtons) do if (v:GetSelected()) then lastSelected = v.faction end if (IsValid(v)) then v:Remove() end end self.factionButtons = {} for _, v in SortedPairs(ix.faction.teams) do if (ix.faction.HasWhitelist(v.index)) then local button = self.factionButtonsPanel:Add("ixMenuSelectionButton") button:SetBackgroundColor(v.color or color_white) button:SetText(L(v.name):utf8upper()) button:SizeToContents() button:SetButtonList(self.factionButtons) button.faction = v.index button.OnSelected = function(panel) local faction = ix.faction.indices[panel.faction] local models = faction:GetModels(LocalPlayer()) self.payload:Set("faction", panel.faction) self.payload:Set("model", math.random(1, #models)) end if ((lastSelected and lastSelected == v.index) or (!lastSelected and v.isDefault)) then button:SetSelected(true) lastSelected = v.index end end end end -- remove panels created for character vars for i = 1, #self.repopulatePanels do self.repopulatePanels[i]:Remove() end self.repopulatePanels = {} -- payload is empty because we attempted to send it - for whatever reason we're back here again so we need to repopulate if (!self.payload.faction) then for _, v in pairs(self.factionButtons) do if (v:GetSelected()) then v:SetSelected(true) break end end end self.factionButtonsPanel:SizeToContents() local zPos = 1 -- set up character vars for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do if (!v.bNoDisplay and k != "__SortedIndex") then local container = self:GetContainerPanel(v.category or "description") if (v.ShouldDisplay and v:ShouldDisplay(container, self.payload) == false) then continue end local panel -- if the var has a custom way of displaying, we'll use that instead if (v.OnDisplay) then panel = v:OnDisplay(container, self.payload) elseif (isstring(v.default)) then panel = container:Add("ixTextEntry") panel:Dock(TOP) panel:SetFont("ixMenuButtonHugeFont") panel:SetUpdateOnType(true) panel.OnValueChange = function(this, text) self.payload:Set(k, text) end end if (IsValid(panel)) then -- add label for entry local label = container:Add("DLabel") label:SetFont("ixMenuButtonLabelFont") label:SetText(L(k):utf8upper()) label:SizeToContents() label:DockMargin(0, 16, 0, 2) label:Dock(TOP) -- we need to set the docking order so the label is above the panel label:SetZPos(zPos - 1) panel:SetZPos(zPos) self:AttachCleanup(label) self:AttachCleanup(panel) if (v.OnPostSetup) then v:OnPostSetup(panel, self.payload) end zPos = zPos + 2 end end end if (!self.bInitialPopulate) then -- setup progress bar segments if (#self.factionButtons > 1) then self.progress:AddSegment("@faction") end self.progress:AddSegment("@description") if (#self.attributesPanel:GetChildren() > 1) then self.progress:AddSegment("@skills") end -- we don't need to show the progress bar if there's only one segment if (#self.progress:GetSegments() == 1) then self.progress:SetVisible(false) end end self.bInitialPopulate = true end function PANEL:VerifyProgression(name) for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do if (name ~= nil and (v.category or "description") != name) then continue end local value = self.payload[k] if (!v.bNoDisplay or v.OnValidate) then if (v.OnValidate) then local result = {v:OnValidate(value, self.payload, LocalPlayer())} if (result[1] == false) then self:GetParent():ShowNotice(3, L(unpack(result, 2))) return false end end self.payload[k] = value end end return true end function PANEL:Paint(width, height) derma.SkinFunc("PaintCharacterCreateBackground", self, width, height) BaseClass.Paint(self, width, height) end vgui.Register("ixCharMenuNew", PANEL, "ixCharMenuPanel") ================================================ FILE: gamemode/core/derma/cl_charload.lua ================================================ local errorModel = "models/error.mdl" local PANEL = {} AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) local function SetCharacter(self, character) self.character = character if (character) then self:SetModel(character:GetModel()) self:SetSkin(character:GetData("skin", 0)) for i = 0, (self:GetNumBodyGroups() - 1) do self:SetBodygroup(i, 0) end local bodygroups = character:GetData("groups", nil) if (istable(bodygroups)) then for k, v in pairs(bodygroups) do self:SetBodygroup(k, v) end end else self:SetModel(errorModel) end end local function GetCharacter(self) return self.character end function PANEL:Init() self.activeCharacter = ClientsideModel(errorModel) self.activeCharacter:SetNoDraw(true) self.activeCharacter.SetCharacter = SetCharacter self.activeCharacter.GetCharacter = GetCharacter self.lastCharacter = ClientsideModel(errorModel) self.lastCharacter:SetNoDraw(true) self.lastCharacter.SetCharacter = SetCharacter self.lastCharacter.GetCharacter = GetCharacter self.animationTime = 0.5 self.shadeY = 0 self.shadeHeight = 0 self.cameraPosition = Vector(80, 0, 35) self.cameraAngle = Angle(0, 180, 0) self.lastPaint = 0 end function PANEL:ResetSequence(model, lastModel) local sequence = model:LookupSequence("idle_unarmed") if (sequence <= 0) then sequence = model:SelectWeightedSequence(ACT_IDLE) end if (sequence > 0) then model:ResetSequence(sequence) else local found = false for _, v in ipairs(model:GetSequenceList()) do if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then model:ResetSequence(v) found = true break end end if (!found) then model:ResetSequence(4) end end model:SetIK(false) -- copy cycle if we can to avoid a jarring transition from resetting the sequence if (lastModel) then model:SetCycle(lastModel:GetCycle()) end end function PANEL:RunAnimation(model) model:FrameAdvance((RealTime() - self.lastPaint) * 0.5) end function PANEL:LayoutEntity(model) model:SetIK(false) self:RunAnimation(model) end function PANEL:SetActiveCharacter(character) self.shadeY = self:GetTall() self.shadeHeight = self:GetTall() -- set character immediately if we're an error (something isn't selected yet) if (self.activeCharacter:GetModel() == errorModel) then self.activeCharacter:SetCharacter(character) self:ResetSequence(self.activeCharacter) return end -- if the animation is already playing, we update its parameters so we can avoid restarting local shade = self:GetTweenAnimation(1) local shadeHide = self:GetTweenAnimation(2) if (shade) then shade.newCharacter = character return elseif (shadeHide) then shadeHide.queuedCharacter = character return end self.lastCharacter:SetCharacter(self.activeCharacter:GetCharacter()) self:ResetSequence(self.lastCharacter, self.activeCharacter) shade = self:CreateAnimation(self.animationTime * 0.5, { index = 1, target = { shadeY = 0, shadeHeight = self:GetTall() }, easing = "linear", OnComplete = function(shadeAnimation, shadePanel) shadePanel.activeCharacter:SetCharacter(shadeAnimation.newCharacter) shadePanel:ResetSequence(shadePanel.activeCharacter) shadePanel:CreateAnimation(shadePanel.animationTime, { index = 2, target = {shadeHeight = 0}, easing = "outQuint", OnComplete = function(animation, panel) if (animation.queuedCharacter) then panel:SetActiveCharacter(animation.queuedCharacter) else panel.lastCharacter:SetCharacter(nil) end end }) end }) shade.newCharacter = character end function PANEL:Paint(width, height) local x, y = self:LocalToScreen(0, 0) local bTransition = self.lastCharacter:GetModel() != errorModel local modelFOV = (ScrW() > ScrH() * 1.8) and 92 or 70 cam.Start3D(self.cameraPosition, self.cameraAngle, modelFOV, x, y, width, height) render.SuppressEngineLighting(true) render.SetLightingOrigin(self.activeCharacter:GetPos()) -- setup lighting render.SetModelLighting(0, 1.5, 1.5, 1.5) for i = 1, 4 do render.SetModelLighting(i, 0.4, 0.4, 0.4) end render.SetModelLighting(5, 0.04, 0.04, 0.04) -- clip anything out of bounds local curparent = self local rightx = self:GetWide() local leftx = 0 local topy = 0 local bottomy = self:GetTall() local previous = curparent while (curparent:GetParent() != nil) do local lastX, lastY = previous:GetPos() curparent = curparent:GetParent() topy = math.Max(lastY, topy + lastY) leftx = math.Max(lastX, leftx + lastX) bottomy = math.Min(lastY + previous:GetTall(), bottomy + lastY) rightx = math.Min(lastX + previous:GetWide(), rightx + lastX) previous = curparent end ix.util.ResetStencilValues() render.SetStencilEnable(true) render.SetStencilWriteMask(30) render.SetStencilTestMask(30) render.SetStencilReferenceValue(31) render.SetStencilCompareFunction(STENCIL_ALWAYS) render.SetStencilPassOperation(STENCIL_REPLACE) render.SetStencilFailOperation(STENCIL_KEEP) render.SetStencilZFailOperation(STENCIL_KEEP) self:LayoutEntity(self.activeCharacter) if (bTransition) then -- only need to layout while it's used self:LayoutEntity(self.lastCharacter) render.SetScissorRect(leftx, topy, rightx, bottomy - (self:GetTall() - self.shadeHeight), true) self.lastCharacter:DrawModel() render.SetScissorRect(leftx, topy + self.shadeHeight, rightx, bottomy, true) self.activeCharacter:DrawModel() render.SetScissorRect(leftx, topy, rightx, bottomy, true) else self.activeCharacter:DrawModel() end render.SetStencilCompareFunction(STENCIL_EQUAL) render.SetStencilPassOperation(STENCIL_KEEP) cam.Start2D() derma.SkinFunc("PaintCharacterTransitionOverlay", self, 0, self.shadeY, width, self.shadeHeight) cam.End2D() render.SetStencilEnable(false) render.SetScissorRect(0, 0, 0, 0, false) render.SuppressEngineLighting(false) cam.End3D() self.lastPaint = RealTime() end function PANEL:OnRemove() self.lastCharacter:Remove() self.activeCharacter:Remove() end vgui.Register("ixCharMenuCarousel", PANEL, "Panel") -- character load panel PANEL = {} AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) AccessorFunc(PANEL, "backgroundFraction", "BackgroundFraction", FORCE_NUMBER) function PANEL:Init() local parent = self:GetParent() local padding = self:GetPadding() local halfWidth = parent:GetWide() * 0.5 - (padding * 2) local halfHeight = parent:GetTall() * 0.5 - (padding * 2) local modelFOV = (ScrW() > ScrH() * 1.8) and 102 or 78 self.animationTime = 1 self.backgroundFraction = 1 -- main panel self.panel = self:AddSubpanel("main") self.panel:SetTitle("loadTitle") self.panel.OnSetActive = function() self:CreateAnimation(self.animationTime, { index = 2, target = {backgroundFraction = 1}, easing = "outQuint", }) end -- character button list local controlList = self.panel:Add("Panel") controlList:Dock(LEFT) controlList:SetSize(halfWidth, halfHeight) local back = controlList:Add("ixMenuButton") back:Dock(BOTTOM) back:SetText("return") back:SizeToContents() back.DoClick = function() self:SlideDown() parent.mainPanel:Undim() end self.characterList = controlList:Add("ixCharMenuButtonList") self.characterList.buttons = {} self.characterList:Dock(FILL) -- right-hand side with carousel and buttons local infoPanel = self.panel:Add("Panel") infoPanel:Dock(FILL) local infoButtons = infoPanel:Add("Panel") infoButtons:Dock(BOTTOM) infoButtons:SetTall(back:GetTall()) -- hmm... local continueButton = infoButtons:Add("ixMenuButton") continueButton:Dock(FILL) continueButton:SetText("choose") continueButton:SetContentAlignment(6) continueButton:SizeToContents() continueButton.DoClick = function() self:SlideDown(self.animationTime, function() net.Start("ixCharacterChoose") net.WriteUInt(self.character:GetID(), 32) net.SendToServer() end, true) end local deleteButton = infoButtons:Add("ixMenuButton") deleteButton:Dock(LEFT) deleteButton:SetText("delete") deleteButton:SetContentAlignment(5) deleteButton:SetTextInset(0, 0) deleteButton:SizeToContents() deleteButton:SetTextColor(derma.GetColor("Error", deleteButton)) deleteButton.DoClick = function() self:SetActiveSubpanel("delete") end self.carousel = infoPanel:Add("ixCharMenuCarousel") self.carousel:Dock(FILL) -- character deletion panel self.delete = self:AddSubpanel("delete") self.delete:SetTitle(nil) self.delete.OnSetActive = function() self.deleteModel:SetModel(self.character:GetModel()) self:CreateAnimation(self.animationTime, { index = 2, target = {backgroundFraction = 0}, easing = "outQuint" }) end local deleteInfo = self.delete:Add("Panel") deleteInfo:SetSize(parent:GetWide() * 0.5, parent:GetTall()) deleteInfo:Dock(LEFT) local deleteReturn = deleteInfo:Add("ixMenuButton") deleteReturn:Dock(BOTTOM) deleteReturn:SetText("no") deleteReturn:SizeToContents() deleteReturn.DoClick = function() self:SetActiveSubpanel("main") end local deleteConfirm = self.delete:Add("ixMenuButton") deleteConfirm:Dock(BOTTOM) deleteConfirm:SetText("yes") deleteConfirm:SetContentAlignment(6) deleteConfirm:SizeToContents() deleteConfirm:SetTextColor(derma.GetColor("Error", deleteConfirm)) deleteConfirm.DoClick = function() local id = self.character:GetID() parent:ShowNotice(1, L("deleteComplete", self.character:GetName())) self:Populate(id) self:SetActiveSubpanel("main") net.Start("ixCharacterDelete") net.WriteUInt(id, 32) net.SendToServer() end self.deleteModel = deleteInfo:Add("ixModelPanel") self.deleteModel:Dock(FILL) self.deleteModel:SetModel(errorModel) self.deleteModel:SetFOV(modelFOV) self.deleteModel.PaintModel = self.deleteModel.Paint local deleteNag = self.delete:Add("Panel") deleteNag:SetTall(parent:GetTall() * 0.5) deleteNag:Dock(BOTTOM) local deleteTitle = deleteNag:Add("DLabel") deleteTitle:SetFont("ixTitleFont") deleteTitle:SetText(L("areYouSure"):utf8upper()) deleteTitle:SetTextColor(ix.config.Get("color")) deleteTitle:SizeToContents() deleteTitle:Dock(TOP) local deleteText = deleteNag:Add("DLabel") deleteText:SetFont("ixMenuButtonFont") deleteText:SetText(L("deleteConfirm")) deleteText:SetTextColor(color_white) deleteText:SetContentAlignment(7) deleteText:Dock(FILL) -- finalize setup self:SetActiveSubpanel("main", 0) end function PANEL:OnCharacterDeleted(character) if (self.bActive and #ix.characters == 0) then self:SlideDown() end end function PANEL:Populate(ignoreID) self.characterList:Clear() self.characterList.buttons = {} local bSelected -- loop backwards to preserve order since we're docking to the bottom for i = 1, #ix.characters do local id = ix.characters[i] local character = ix.char.loaded[id] if (!character or character:GetID() == ignoreID) then continue end local index = character:GetFaction() local faction = ix.faction.indices[index] local color = faction and faction.color or color_white local button = self.characterList:Add("ixMenuSelectionButton") button:SetBackgroundColor(color) button:SetText(character:GetName()) button:SizeToContents() button:SetButtonList(self.characterList.buttons) button.character = character button.OnSelected = function(panel) self:OnCharacterButtonSelected(panel) end -- select currently loaded character if available local localCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() if (localCharacter and character:GetID() == localCharacter:GetID()) then button:SetSelected(true) self.characterList:ScrollToChild(button) bSelected = true end end if (!bSelected) then local buttons = self.characterList.buttons if (#buttons > 0) then local button = buttons[#buttons] button:SetSelected(true) self.characterList:ScrollToChild(button) else self.character = nil end end self.characterList:SizeToContents() end function PANEL:OnSlideUp() self.bActive = true self:Populate() end function PANEL:OnSlideDown() self.bActive = false end function PANEL:OnCharacterButtonSelected(panel) self.carousel:SetActiveCharacter(panel.character) self.character = panel.character end function PANEL:Paint(width, height) derma.SkinFunc("PaintCharacterLoadBackground", self, width, height) end vgui.Register("ixCharMenuLoad", PANEL, "ixCharMenuPanel") ================================================ FILE: gamemode/core/derma/cl_classes.lua ================================================ local PANEL = {} function PANEL:Init() self:SetTall(64) local function AssignClick(panel) panel.OnMousePressed = function() self.pressing = -1 self:OnClick() end panel.OnMouseReleased = function() if (self.pressing) then self.pressing = nil end end end self.icon = self:Add("SpawnIcon") self.icon:SetSize(128, 64) self.icon:InvalidateLayout(true) self.icon:Dock(LEFT) self.icon.PaintOver = function(this, w, h) end AssignClick(self.icon) self.limit = self:Add("DLabel") self.limit:Dock(RIGHT) self.limit:SetMouseInputEnabled(true) self.limit:SetCursor("hand") self.limit:SetExpensiveShadow(1, Color(0, 0, 60)) self.limit:SetContentAlignment(5) self.limit:SetFont("ixMediumFont") self.limit:SetWide(64) AssignClick(self.limit) self.label = self:Add("DLabel") self.label:Dock(FILL) self.label:SetMouseInputEnabled(true) self.label:SetCursor("hand") self.label:SetExpensiveShadow(1, Color(0, 0, 60)) self.label:SetContentAlignment(5) self.label:SetFont("ixMediumFont") AssignClick(self.label) end function PANEL:OnClick() ix.command.Send("BecomeClass", self.class) end function PANEL:SetNumber(number) local limit = self.data.limit if (limit > 0) then self.limit:SetText(Format("%s/%s", number, limit)) else self.limit:SetText("∞") end end function PANEL:SetClass(data) if (data.model) then local model = data.model if (istable(model)) then model = model[ math.random(#model) ] end self.icon:SetModel(model) else local char = LocalPlayer():GetCharacter() local model = LocalPlayer():GetModel() if (char) then model = char:GetModel() end self.icon:SetModel(model) end self.label:SetText(L(data.name)) self.data = data self.class = data.index self:SetNumber(#ix.class.GetPlayers(data.index)) end vgui.Register("ixClassPanel", PANEL, "DPanel") PANEL = {} function PANEL:Init() ix.gui.classes = self self:SetSize(self:GetParent():GetSize()) self.list = vgui.Create("DPanelList", self) self.list:Dock(FILL) self.list:EnableVerticalScrollbar() self.list:SetSpacing(5) self.list:SetPadding(5) self.classPanels = {} self:LoadClasses() end function PANEL:LoadClasses() self.list:Clear() for k, v in ipairs(ix.class.list) do local no, why = ix.class.CanSwitchTo(LocalPlayer(), k) local itsFull = ("class is full" == why) if (no or itsFull) then local panel = vgui.Create("ixClassPanel", self.list) panel:SetClass(v) table.insert(self.classPanels, panel) self.list:AddItem(panel) end end end vgui.Register("ixClasses", PANEL, "EditablePanel") hook.Add("CreateMenuButtons", "ixClasses", function(tabs) local cnt = table.Count(ix.class.list) if (cnt <= 1) then return end for k, _ in ipairs(ix.class.list) do if (!ix.class.CanSwitchTo(LocalPlayer(), k)) then continue else tabs["classes"] = function(container) container:Add("ixClasses") end return end end end) net.Receive("ixClassUpdate", function() local client = net.ReadEntity() if (ix.gui.classes and ix.gui.classes:IsVisible()) then if (client == LocalPlayer()) then ix.gui.classes:LoadClasses() else for _, v in ipairs(ix.gui.classes.classPanels) do local data = v.data v:SetNumber(#ix.class.GetPlayers(data.index)) end end end end) ================================================ FILE: gamemode/core/derma/cl_config.lua ================================================ -- config manager local PANEL = {} function PANEL:Init() self:Dock(FILL) self:SetSearchEnabled(true) self:Populate() end function PANEL:Populate() -- gather categories local categories = {} local categoryIndices = {} for k, v in pairs(ix.config.stored) do local index = v.data and v.data.category or "misc" categories[index] = categories[index] or {} categories[index][k] = v end -- sort by category phrase for k, _ in pairs(categories) do categoryIndices[#categoryIndices + 1] = k end table.sort(categoryIndices, function(a, b) return L(a) < L(b) end) -- add panels for _, category in ipairs(categoryIndices) do local categoryPhrase = L(category) self:AddCategory(categoryPhrase) -- we can use sortedpairs since configs don't have phrases to account for for k, v in SortedPairs(categories[category]) do if (isfunction(v.hidden) and v.hidden()) then continue end local data = v.data.data local type = v.type local value = ix.util.SanitizeType(type, ix.config.Get(k)) local row = self:AddRow(type, categoryPhrase) row:SetText(ix.util.ExpandCamelCase(k)) row:Populate(k, v.data) -- type-specific properties if (type == ix.type.number) then row:SetMin(data and data.min or 0) row:SetMax(data and data.max or 1) row:SetDecimals(data and data.decimals or 0) end row:SetValue(value, true) row:SetShowReset(value != v.default, k, v.default) row.OnValueChanged = function(panel) local newValue = ix.util.SanitizeType(type, panel:GetValue()) panel:SetShowReset(newValue != v.default, k, v.default) net.Start("ixConfigSet") net.WriteString(k) net.WriteType(newValue) net.SendToServer() end row.OnResetClicked = function(panel) panel:SetValue(v.default, true) panel:SetShowReset(false) net.Start("ixConfigSet") net.WriteString(k) net.WriteType(v.default) net.SendToServer() end row:GetLabel():SetHelixTooltip(function(tooltip) local title = tooltip:AddRow("name") title:SetImportant() title:SetText(k) title:SizeToContents() title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5)) local description = tooltip:AddRow("description") description:SetText(v.description) description:SizeToContents() end) end end self:SizeToContents() end vgui.Register("ixConfigManager", PANEL, "ixSettings") -- plugin manager PANEL = {} function PANEL:Init() self:Dock(FILL) self:SetSearchEnabled(true) self.loadedCategory = L("loadedPlugins") self.unloadedCategory = L("unloadedPlugins") if (!ix.gui.bReceivedUnloadedPlugins) then net.Start("ixConfigRequestUnloadedList") net.SendToServer() end self:Populate() end function PANEL:OnPluginToggled(uniqueID, bEnabled) net.Start("ixConfigPluginToggle") net.WriteString(uniqueID) net.WriteBool(bEnabled) net.SendToServer() end function PANEL:Populate() self:AddCategory(self.loadedCategory) self:AddCategory(self.unloadedCategory) -- add loaded plugins for k, v in SortedPairsByMemberValue(ix.plugin.list, "name") do local row = self:AddRow(ix.type.bool, self.loadedCategory) row.id = k row.setting:SetEnabledText(L("on"):utf8upper()) row.setting:SetDisabledText(L("off"):utf8upper()) row.setting:SizeToContents() -- if this plugin is not in the unloaded list currently, then it's queued for an unload row:SetValue(!ix.plugin.unloaded[k], true) row:SetText(v.name) row.OnValueChanged = function(panel, bEnabled) self:OnPluginToggled(k, bEnabled) end row:GetLabel():SetHelixTooltip(function(tooltip) local title = tooltip:AddRow("name") title:SetImportant() title:SetText(v.name) title:SizeToContents() title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5)) local description = tooltip:AddRow("description") description:SetText(v.description) description:SizeToContents() end) end self:UpdateUnloaded(true) self:SizeToContents() end function PANEL:UpdatePlugin(uniqueID, bEnabled) for _, v in pairs(self:GetRows()) do if (v.id == uniqueID) then v:SetValue(bEnabled, true) end end end -- called from Populate and from the ixConfigUnloadedList net message function PANEL:UpdateUnloaded(bNoSizeToContents) for _, v in pairs(self:GetRows()) do if (ix.plugin.unloaded[v.id]) then v:SetValue(false, true) end end for k, v in SortedPairs(ix.plugin.unloaded) do if (ix.plugin.list[k]) then -- if this plugin is in the loaded plugins list then it's queued for an unload - don't display it in this category continue end local row = self:AddRow(ix.type.bool, self.unloadedCategory) row.id = k row.setting:SetEnabledText(L("on"):utf8upper()) row.setting:SetDisabledText(L("off"):utf8upper()) row.setting:SizeToContents() row:SetText(k) row:SetValue(!v, true) row.OnValueChanged = function(panel, bEnabled) self:OnPluginToggled(k, bEnabled) end end if (!bNoSizeToContents) then self:SizeToContents() end end vgui.Register("ixPluginManager", PANEL, "ixSettings") ================================================ FILE: gamemode/core/derma/cl_credits.lua ================================================ local CREDITS = { {"Alex Grist", "76561197979205163", {"creditLeadDeveloper", "creditManager"}}, {"Igor Radovanovic", "76561197990111113", {"creditLeadDeveloper", "creditUIDesigner"}}, {"Jaydawg", "76561197970371430", {"creditTester"}} } local SPECIALS = { { {"Luna", "76561197988658543"}, {"Rain GBizzle", "76561198036111376"} }, { {"Black Tea", "76561197999893894"} } } local MISC = { {"nebulous", "Staff members finding bugs and providing input"}, {"Contributors", "Ongoing support from various developers via GitHub"}, {"NutScript", "Providing the base framework to build upon"} } local url = "https://gethelix.co/" local padding = 32 -- logo local PANEL = {} function PANEL:Init() self:SetTall(ScrH() * 0.60) self:Dock(TOP) end function PANEL:Paint(width, height) derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25) -- title surface.SetFont("ixIntroSubtitleFont") local text = L("helix"):lower() local textWidth, textHeight = surface.GetTextSize(text) surface.SetTextColor(color_white) surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5) surface.DrawText(text) -- version surface.SetFont("ixMenuMiniFont") surface.SetTextColor(200, 200, 200, 255) surface.SetTextPos(width * 0.5 + textWidth * 0.5, height * 0.5 - textHeight * 0.5) surface.DrawText(GAMEMODE.Version) end vgui.Register("ixCreditsLogo", PANEL, "Panel") -- nametag PANEL = {} function PANEL:Init() self.name = self:Add("DLabel") self.name:SetFont("ixMenuButtonFontThick") self.avatar = self:Add("AvatarImage") end function PANEL:SetName(name) self.name:SetText(name) end function PANEL:SetAvatar(steamid) self.avatar:SetSteamID(steamid, 64) end function PANEL:PerformLayout(width, height) self.name:SetPos(width - self.name:GetWide(), 0) self.avatar:MoveLeftOf(self.name, padding * 0.5) end function PANEL:SizeToContents() self.name:SizeToContents() local tall = self.name:GetTall() self.avatar:SetSize(tall, tall) self:SetSize(self.name:GetWide() + self.avatar:GetWide() + padding * 0.5, self.name:GetTall()) end vgui.Register("ixCreditsNametag", PANEL, "Panel") -- name row PANEL = {} function PANEL:Init() self:DockMargin(0, padding, 0, 0) self:Dock(TOP) self.nametag = self:Add("ixCreditsNametag") self.tags = self:Add("DLabel") self.tags:SetFont("ixMenuButtonFont") self:SizeToContents() end function PANEL:SetName(name) self.nametag:SetName(name) end function PANEL:SetAvatar(steamid) self.nametag:SetAvatar(steamid) end function PANEL:SetTags(tags) for i = 1, #tags do tags[i] = L(tags[i]) end self.tags:SetText(table.concat(tags, "\n")) end function PANEL:Paint(width, height) surface.SetDrawColor(ix.config.Get("color")) surface.DrawRect(width * 0.5 - 1, 0, 1, height) end function PANEL:PerformLayout(width, height) self.nametag:SetPos(width * 0.5 - self.nametag:GetWide() - padding, 0) self.tags:SetPos(width * 0.5 + padding, 0) end function PANEL:SizeToContents() self.nametag:SizeToContents() self.tags:SizeToContents() self:SetTall(math.max(self.nametag:GetTall(), self.tags:GetTall())) end vgui.Register("ixCreditsRow", PANEL, "Panel") PANEL = {} function PANEL:Init() self.left = {} self.right = {} end function PANEL:AddLeft(name, steamid) local nametag = self:Add("ixCreditsNametag") nametag:SetName(name) nametag:SetAvatar(steamid) nametag:SizeToContents() self.left[#self.left + 1] = nametag end function PANEL:AddRight(name, steamid) local nametag = self:Add("ixCreditsNametag") nametag:SetName(name) nametag:SetAvatar(steamid) nametag:SizeToContents() self.right[#self.right + 1] = nametag end function PANEL:PerformLayout(width, height) local y = 0 for _, v in ipairs(self.left) do v:SetPos(width * 0.25 - v:GetWide() * 0.5, y) y = y + v:GetTall() + padding end y = 0 for _, v in ipairs(self.right) do v:SetPos(width * 0.75 - v:GetWide() * 0.5, y) y = y + v:GetTall() + padding end if (IsValid(self.center)) then self.center:SetPos(width * 0.5 - self.center:GetWide() * 0.5, y) end end function PANEL:SizeToContents() local heightLeft, heightRight, centerHeight = 0, 0, 0 if (#self.left > #self.right) then local center = self.left[#self.left] centerHeight = center:GetTall() self.center = center self.left[#self.left] = nil elseif (#self.right > #self.left) then local center = self.right[#self.right] centerHeight = center:GetTall() self.center = center self.right[#self.right] = nil end for _, v in ipairs(self.left) do heightLeft = heightLeft + v:GetTall() + padding end for _, v in ipairs(self.right) do heightRight = heightRight + v:GetTall() + padding end self:SetTall(math.max(heightLeft, heightRight) + centerHeight) end vgui.Register("ixCreditsSpecials", PANEL, "Panel") PANEL = {} function PANEL:Init() self:Add("ixCreditsLogo") local link = self:Add("DLabel", self) link:SetFont("ixMenuMiniFont") link:SetTextColor(Color(200, 200, 200, 255)) link:SetText(url) link:SetContentAlignment(5) link:Dock(TOP) link:SizeToContents() link:SetMouseInputEnabled(true) link:SetCursor("hand") link.OnMousePressed = function() gui.OpenURL(url) end for _, v in ipairs(CREDITS) do local row = self:Add("ixCreditsRow") row:SetName(v[1]) row:SetAvatar(v[2]) row:SetTags(v[3]) row:SizeToContents() end local specials = self:Add("ixLabel") specials:SetFont("ixMenuButtonFont") specials:SetText(L("creditSpecial"):utf8upper()) specials:SetTextColor(ix.config.Get("color")) specials:SetDropShadow(1) specials:SetKerning(16) specials:SetContentAlignment(5) specials:DockMargin(0, padding * 2, 0, padding) specials:Dock(TOP) specials:SizeToContents() local specialList = self:Add("ixCreditsSpecials") specialList:DockMargin(0, padding, 0, 0) specialList:Dock(TOP) for _, v in ipairs(SPECIALS[1]) do specialList:AddLeft(v[1], v[2]) end for _, v in ipairs(SPECIALS[2]) do specialList:AddRight(v[1], v[2]) end specialList:SizeToContents() -- less more padding if there's a center column nametag if (IsValid(specialList.center)) then specialList:DockMargin(0, padding, 0, padding) end for _, v in ipairs(MISC) do local title = self:Add("DLabel") title:SetFont("ixMenuButtonFontThick") title:SetText(v[1]) title:SetContentAlignment(5) title:SizeToContents() title:DockMargin(0, padding, 0, 0) title:Dock(TOP) local description = self:Add("DLabel") description:SetFont("ixSmallTitleFont") description:SetText(v[2]) description:SetContentAlignment(5) description:SizeToContents() description:Dock(TOP) end self:Dock(TOP) self:SizeToContents() end function PANEL:SizeToContents() local height = padding for _, v in pairs(self:GetChildren()) do local _, top, _, bottom = v:GetDockMargin() height = height + v:GetTall() + top + bottom end self:SetTall(height) end vgui.Register("ixCredits", PANEL, "Panel") hook.Add("PopulateHelpMenu", "ixCredits", function(tabs) tabs["credits"] = function(container) container:Add("ixCredits") end end) ================================================ FILE: gamemode/core/derma/cl_deathscreen.lua ================================================ local PANEL = {} function PANEL:Init() local scrW, scrH = ScrW(), ScrH() self:SetSize(scrW, scrH) self:SetPos(0, 0) local text = string.utf8upper(L("youreDead")) surface.SetFont("ixMenuButtonHugeFont") local textW, textH = surface.GetTextSize(text) self.label = self:Add("DLabel") self.label:SetPaintedManually(true) self.label:SetPos(scrW * 0.5 - textW * 0.5, scrH * 0.5 - textH * 0.5) self.label:SetFont("ixMenuButtonHugeFont") self.label:SetText(text) self.label:SizeToContents() self.progress = 0 self:CreateAnimation(ix.config.Get("spawnTime", 5), { bIgnoreConfig = true, target = {progress = 1}, OnComplete = function(animation, panel) if (!panel:IsClosing()) then panel:Close() end end }) end function PANEL:Think() self.label:SetAlpha(((self.progress - 0.3) / 0.3) * 255) end function PANEL:IsClosing() return self.bIsClosing end function PANEL:Close() self.bIsClosing = true self:CreateAnimation(2, { index = 2, bIgnoreConfig = true, target = {progress = 0}, OnComplete = function(animation, panel) panel:Remove() end }) end function PANEL:Paint(width, height) derma.SkinFunc("PaintDeathScreenBackground", self, width, height, self.progress) self.label:PaintManual() derma.SkinFunc("PaintDeathScreen", self, width, height, self.progress) end vgui.Register("ixDeathScreen", PANEL, "Panel") ================================================ FILE: gamemode/core/derma/cl_dev_icon.lua ================================================ --Icon Editor Base and Math Scale Functions from: https://github.com/TeslaCloud/flux-ce/tree/master local scaleFactorX = 1 / 1920 local scaleFactorY = 1 / 1080 local function scale(size) return math.floor(size * (ScrH() * scaleFactorY)) end local function scale_x(size) return math.floor(size * (ScrW() * scaleFactorX)) end local PANEL = {} function PANEL:Init() if (IsValid(ix.gui.dev_icon)) then ix.gui.dev_icon:Remove() end ix.gui.dev_icon = self local pW, pH = ScrW() * 0.6, ScrH() * 0.6 self:SetSize(pW, pH) self:MakePopup() self:Center() local buttonSize = scale(48) self.model = vgui.Create("DAdjustableModelPanel", self) self.model:SetSize(pW * 0.5, pH - buttonSize) self.model:Dock(LEFT) self.model:DockMargin(0, 0, 0, buttonSize + scale(8)) self.model:SetModel("models/props_borealis/bluebarrel001.mdl") self.model:SetLookAt(Vector(0, 0, 0)) self.model.LayoutEntity = function() end local x = scale_x(4) self.best = vgui.Create("DButton", self) self.best:SetSize(buttonSize, buttonSize) self.best:SetPos(x, pH - buttonSize - scale(4)) self.best:SetFont("ixIconsMenuButton") self.best:SetText("b") self.best:SetTooltip(L("iconEditorAlignBest")) self.best.DoClick = function() local entity = self.model:GetEntity() local pos = entity:GetPos() local camData = PositionSpawnIcon(entity, pos) if (camData) then self.model:SetCamPos(camData.origin) self.model:SetFOV(camData.fov) self.model:SetLookAng(camData.angles) end end x = x + buttonSize + scale_x(4) self.front = vgui.Create("DButton", self) self.front:SetSize(buttonSize, buttonSize) self.front:SetPos(x, pH - buttonSize - scale(4)) self.front:SetFont("ixIconsMenuButton") self.front:SetText("m") self.front:SetTooltip(L("iconEditorAlignFront")) self.front.DoClick = function() local entity = self.model:GetEntity() local pos = entity:GetPos() local camPos = pos + Vector(-200, 0, 0) self.model:SetCamPos(camPos) self.model:SetFOV(45) self.model:SetLookAng((camPos * -1):Angle()) end x = x + buttonSize + scale_x(4) self.above = vgui.Create("DButton", self) self.above:SetSize(buttonSize, buttonSize) self.above:SetPos(x, pH - buttonSize - scale(4)) self.above:SetFont("ixIconsMenuButton") self.above:SetText("u") self.above:SetTooltip(L("iconEditorAlignAbove")) self.above.DoClick = function() local entity = self.model:GetEntity() local pos = entity:GetPos() local camPos = pos + Vector(0, 0, 200) self.model:SetCamPos(camPos) self.model:SetFOV(45) self.model:SetLookAng((camPos * -1):Angle()) end x = x + buttonSize + scale_x(4) self.right = vgui.Create("DButton", self) self.right:SetSize(buttonSize, buttonSize) self.right:SetPos(x, pH - buttonSize - scale(4)) self.right:SetFont("ixIconsMenuButton") self.right:SetText("t") self.right:SetTooltip(L("iconEditorAlignRight")) self.right.DoClick = function() local entity = self.model:GetEntity() local pos = entity:GetPos() local camPos = pos + Vector(0, 200, 0) self.model:SetCamPos(camPos) self.model:SetFOV(45) self.model:SetLookAng((camPos * -1):Angle()) end x = x + buttonSize + scale_x(4) self.center = vgui.Create("DButton", self) self.center:SetSize(buttonSize, buttonSize) self.center:SetPos(x, pH - buttonSize - scale(4)) self.center:SetFont("ixIconsMenuButton") self.center:SetText("T") self.center:SetTooltip(L("iconEditorAlignCenter")) self.center.DoClick = function() local entity = self.model:GetEntity() local pos = entity:GetPos() self.model:SetCamPos(pos) self.model:SetFOV(45) self.model:SetLookAng(Angle(0, -180, 0)) end self.best:DoClick() self.preview = vgui.Create("DPanel", self) self.preview:Dock(FILL) self.preview:DockMargin(scale_x(4), 0, 0, 0) self.preview:DockPadding(scale_x(4), scale(4), scale_x(4), scale(4)) self.preview.Paint = function(pnl, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100)) end self.modelLabel = self.preview:Add("DLabel") self.modelLabel:Dock(TOP) self.modelLabel:SetText("Model") self.modelLabel:SetFont("ixMenuButtonFontSmall") self.modelLabel:DockMargin(4, 4, 4, 4) self.modelPath = vgui.Create("ixTextEntry", self.preview) self.modelPath:SetValue(self.model:GetModel()) self.modelPath:Dock(TOP) self.modelPath:SetFont("ixMenuButtonFontSmall") self.modelPath:SetPlaceholderText("Model...") self.modelPath.OnEnter = function(pnl) local model = pnl:GetValue() if (model and model != "") then self.model:SetModel(model) self.item:Rebuild() end end self.modelPath.OnLoseFocus = function(pnl) local model = pnl:GetValue() if (model and model != "") then self.model:SetModel(model) self.item:Rebuild() end end self.width = vgui.Create("ixSettingsRowNumber", self.preview) self.width:Dock(TOP) self.width:SetText(L("iconEditorWidth")) self.width:SetMin(1) self.width:SetMax(24) self.width:SetDecimals(0) self.width:SetValue(1) self.width.OnValueChanged = function(pnl, value) self.item:Rebuild() end self.height = vgui.Create("ixSettingsRowNumber", self.preview) self.height:Dock(TOP) self.height:SetText(L("iconEditorHeight")) self.height:SetMin(1) self.height:SetMax(24) self.height:SetDecimals(0) self.height:SetValue(1) self.height.OnValueChanged = function(pnl, value) self.item:Rebuild() end self.itemPanel = vgui.Create("DPanel", self.preview) self.itemPanel:Dock(FILL) self.itemPanel.Paint = function(pnl, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100)) end self.item = vgui.Create("DModelPanel", self.itemPanel) self.item:SetMouseInputEnabled(false) self.item.LayoutEntity = function() end self.item.PaintOver = function(pnl, w, h) surface.SetDrawColor(color_white) surface.DrawOutlinedRect(0, 0, w, h) end self.item.Rebuild = function(pnl) local slotSize = 64 local padding = scale(2) local slotWidth, slotHeight = math.Round(self.width:GetValue()), math.Round(self.height:GetValue()) local w, h = slotWidth * (slotSize + padding) - padding, slotHeight * (slotSize + padding) - padding pnl:SetModel(self.model:GetModel()) pnl:SetCamPos(self.model:GetCamPos()) pnl:SetFOV(self.model:GetFOV()) pnl:SetLookAng(self.model:GetLookAng()) pnl:SetSize(w, h) pnl:Center() end self.item:Rebuild() timer.Create("ix_icon_editor_update", 0.5, 0, function() if IsValid(self) and IsValid(self.model) then self.item:Rebuild() else timer.Remove("ix_icon_editor_update") end end) self.copy = vgui.Create("DButton", self) self.copy:SetSize(buttonSize, buttonSize) self.copy:SetPos(pW - buttonSize - scale_x(12), pH - buttonSize - scale(12)) self.copy:SetFont("ixIconsMenuButton") self.copy:SetText("}") self.copy:SetTooltip(L("iconEditorCopy")) self.copy.DoClick = function() local camPos = self.model:GetCamPos() local camAng = self.model:GetLookAng() local str = "ITEM.model = \""..self.model:GetModel().."\"\n" .."ITEM.width = "..math.Round(self.width:GetValue()).."\n" .."ITEM.height = "..math.Round(self.height:GetValue()).."\n" .."ITEM.iconCam = {\n" .."\tpos = Vector("..math.Round(camPos.x, 2)..", "..math.Round(camPos.y, 2)..", "..math.Round(camPos.z, 2).."),\n" .."\tang = Angle("..math.Round(camAng.p, 2)..", "..math.Round(camAng.y, 2)..", "..math.Round(camAng.r, 2).."),\n" .."\tfov = "..math.Round(self.model:GetFOV(), 2).."\n" .."}\n" SetClipboardText(str) ix.util.Notify(L("iconEditorCopied")) end end vgui.Register("ix_icon_editor", PANEL, "DFrame") concommand.Add("ix_dev_icon", function() if (LocalPlayer():IsAdmin()) then vgui.Create("ix_icon_editor") end end) ================================================ FILE: gamemode/core/derma/cl_entitymenu.lua ================================================ local animationTime = 1 local padding = 32 -- entity menu button DEFINE_BASECLASS("ixMenuButton") local PANEL = {} AccessorFunc(PANEL, "callback", "Callback") function PANEL:Init() self:SetTall(ScrH() * 0.1) self:Dock(TOP) end function PANEL:DoClick() local bStatus = true local parent = ix.menu.panel local entity = parent:GetEntity() if (parent.bClosing) then return end if (isfunction(self.callback)) then bStatus = self.callback() end if (bStatus != false) then ix.menu.NetworkChoice(entity, self.originalText, bStatus) end parent:Remove() end function PANEL:SetText(text) self.originalText = text BaseClass.SetText(self, text) end vgui.Register("ixEntityMenuButton", PANEL, "ixMenuButton") -- entity menu list DEFINE_BASECLASS("EditablePanel") PANEL = {} function PANEL:Init() self.list = {} end function PANEL:AddOption(text, callback) local panel = self:Add("ixEntityMenuButton") panel:SetText(text) panel:SetCallback(callback) panel:Dock(TOP) if (self.bPaintedManually) then panel:SetPaintedManually(true) end self.list[#self.list + 1] = panel end function PANEL:SizeToContents() local height = 0 for i = 1, #self.list do height = height + self.list[i]:GetTall() end self:SetSize(ScrW() * 0.5 - padding * 2, height) end function PANEL:Center() local parent = self:GetParent() self:SetPos( ScrW() * 0.5 + padding, parent:GetTall() * 0.5 - self:GetTall() * 0.5 ) end function PANEL:SetPaintedManually(bValue) if (bValue) then for i = 1, #self.list do self.list[i]:SetPaintedManually(true) end self.bPaintedManually = true end BaseClass.SetPaintedManually(self, bValue) end function PANEL:PaintManual() BaseClass.PaintManual(self) local list = self.list for i = 1, #list do list[i]:PaintManual() end end vgui.Register("ixEntityMenuList", PANEL, "EditablePanel") -- entity menu DEFINE_BASECLASS("EditablePanel") PANEL = {} AccessorFunc(PANEL, "entity", "Entity") AccessorFunc(PANEL, "bClosing", "IsClosing", FORCE_BOOL) AccessorFunc(PANEL, "desiredHeight", "DesiredHeight", FORCE_NUMBER) function PANEL:Init() if (IsValid(ix.menu.panel)) then self:Remove() return end -- close entity tooltip if it's open if (IsValid(ix.gui.entityInfo)) then ix.gui.entityInfo:Remove() end ix.menu.panel = self self:SetSize(ScrW(), ScrH()) self:SetPos(0, 0) self.list = self:Add("ixEntityMenuList") self.list:SetPaintedManually(true) self.desiredHeight = 0 self.blur = 0 self.alpha = 1 self.bClosing = false self.lastPosition = vector_origin self:CreateAnimation(animationTime, { target = {blur = 1}, easing = "outQuint" }) self:MakePopup() end function PANEL:SetOptions(options) for k, v in pairs(options) do self.list:AddOption(k, v) end self.list:SizeToContents() self.list:Center() end function PANEL:GetApproximateScreenHeight(entity, distanceSqr) return IsValid(entity) and (entity:BoundingRadius() * (entity:IsPlayer() and 1.5 or 1) or 0) / math.sqrt(distanceSqr) * self:GetTall() or 0 end function PANEL:Think() local entity = self.entity local distance = 0 if (IsValid(entity)) then local position = entity:GetPos() distance = LocalPlayer():GetShootPos():DistToSqr(position) if (distance > 65536) then self:Remove() return end self.lastPosition = position end self.desiredHeight = math.max(self.list:GetTall() + padding * 2, self:GetApproximateScreenHeight(entity, distance)) end function PANEL:Paint(width, height) -- luacheck: ignore 312 local selfHalf = self:GetTall() * 0.5 local entity = self.entity height = self.desiredHeight + padding * 2 width = self.blur * width local y = selfHalf - height * 0.5 DisableClipping(true) -- for cheap blur render.SetScissorRect(0, y, width, y + height, true) if (IsValid(entity)) then cam.Start3D() ix.util.ResetStencilValues() render.SetStencilEnable(true) cam.IgnoreZ(true) render.SetStencilWriteMask(29) render.SetStencilTestMask(29) render.SetStencilReferenceValue(29) render.SetStencilCompareFunction(STENCIL_ALWAYS) render.SetStencilPassOperation(STENCIL_REPLACE) render.SetStencilFailOperation(STENCIL_KEEP) render.SetStencilZFailOperation(STENCIL_KEEP) entity:DrawModel() render.SetStencilCompareFunction(STENCIL_NOTEQUAL) render.SetStencilPassOperation(STENCIL_KEEP) cam.Start2D() ix.util.DrawBlur(self, 10) cam.End2D() cam.IgnoreZ(false) render.SetStencilEnable(false) cam.End3D() else ix.util.DrawBlur(self, 10) end render.SetScissorRect(0, 0, 0, 0, false) DisableClipping(false) -- scissor again because 3d rendering messes with the clipping apparently? render.SetScissorRect(0, y, width, y + height, true) surface.SetDrawColor(ix.config.Get("color")) surface.DrawRect(ScrW() * 0.5, y + padding, 1, height - padding * 2) self.list:PaintManual() render.SetScissorRect(0, 0, 0, 0, false) end function PANEL:GetOverviewInfo(origin, angles) local entity = self.entity if (IsValid(entity)) then local radius = entity:BoundingRadius() * (entity:IsPlayer() and 0.5 or 1) local center = entity:LocalToWorld(entity:OBBCenter()) + LocalPlayer():GetRight() * radius return LerpAngle(self.bClosing and self.alpha or self.blur, angles, (center - origin):Angle()) end return angles end function PANEL:OnMousePressed(code) if (code == MOUSE_LEFT) then self:Remove() end end function PANEL:Remove() if (self.bClosing) then return end self.bClosing = true self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) gui.EnableScreenClicker(false) self:CreateAnimation(animationTime * 0.5, { target = {alpha = 0}, index = 2, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.alpha * 255) end, OnComplete = function(animation, panel) ix.menu.panel = nil BaseClass.Remove(self) end }) end vgui.Register("ixEntityMenu", PANEL, "EditablePanel") ================================================ FILE: gamemode/core/derma/cl_generic.lua ================================================ -- generic panels that are applicable anywhere -- used for prominent text entries DEFINE_BASECLASS("DTextEntry") local PANEL = {} AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") function PANEL:Init() self:SetPaintBackground(false) self:SetTextColor(color_white) self.backgroundColor = Color(255, 255, 255, 25) end function PANEL:SetFont(font) surface.SetFont(font) local _, height = surface.GetTextSize("W@") self:SetTall(height) BaseClass.SetFont(self, font) end function PANEL:Paint(width, height) derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor) BaseClass.Paint(self, width, height) end vgui.Register("ixTextEntry", PANEL, "DTextEntry") -- similar to a frame, but is mainly used for grouping panels together in a list PANEL = {} AccessorFunc(PANEL, "text", "Text", FORCE_STRING) AccessorFunc(PANEL, "color", "Color") function PANEL:Init() self.text = "" self.paddingTop = 32 local skin = self:GetSkin() if (skin and skin.fontCategoryBlur) then surface.SetFont(skin.fontCategoryBlur) self.paddingTop = select(2, surface.GetTextSize("W@")) + 6 end self:DockPadding(1, self.paddingTop, 1, 1) end function PANEL:SizeToContents() local height = self.paddingTop + 1 for _, v in ipairs(self:GetChildren()) do if (IsValid(v) and v:IsVisible()) then local _, top, _, bottom = v:GetDockMargin() height = height + v:GetTall() + top + bottom end end self:SetTall(height) end function PANEL:Paint(width, height) derma.SkinFunc("PaintCategoryPanel", self, self.text, self.color) end vgui.Register("ixCategoryPanel", PANEL, "EditablePanel") -- segmented progress bar PANEL = {} AccessorFunc(PANEL, "font", "Font", FORCE_STRING) AccessorFunc(PANEL, "barColor", "BarColor") AccessorFunc(PANEL, "textColor", "TextColor") AccessorFunc(PANEL, "progress", "Progress", FORCE_NUMBER) AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) AccessorFunc(PANEL, "easingType", "EasingType", FORCE_STRING) function PANEL:Init() self.segments = {} self.padding = ScrH() * 0.01 self.fraction = 0 self.animationTime = 0.5 self.easingType = "outQuint" self.progress = 0 end function PANEL:AddSegment(text) local id = #self.segments + 1 if (text:sub(1, 1) == "@") then text = L(text:sub(2)) end self.segments[id] = text return id end function PANEL:AddSegments(...) local segments = {...} for i = 1, #segments do self:AddSegment(segments[i]) end end function PANEL:GetSegments() return self.segments end function PANEL:SetProgress(segment) self.progress = math.Clamp(segment, 0, #self.segments) self:CreateAnimation(self.animationTime, { target = {fraction = self.progress / #self.segments}, easing = self.easingType }) end function PANEL:IncrementProgress(amount) self:SetProgress(self.progress + (amount or 1)) end function PANEL:DecrementProgress(amount) self:SetProgress(self.progress - (amount or 1)) end function PANEL:GetFraction() return self.fraction end function PANEL:SizeToContents() self:SetTall(draw.GetFontHeight(self.font or self:GetSkin().fontSegmentedProgress) + self.padding) end function PANEL:Paint(width, height) derma.SkinFunc("PaintSegmentedProgressBackground", self, width, height) if (#self.segments > 0) then derma.SkinFunc("PaintSegmentedProgress", self, width, height) end end vgui.Register("ixSegmentedProgress", PANEL, "Panel") -- list of labelled information PANEL = {} AccessorFunc(PANEL, "labelColor", "LabelColor") AccessorFunc(PANEL, "textColor", "TextColor") AccessorFunc(PANEL, "list", "List") AccessorFunc(PANEL, "minWidth", "MinimumWidth", FORCE_NUMBER) function PANEL:Init() self.label = self:Add("DLabel") self.label:SetFont("ixMediumFont") self.label:SetExpensiveShadow(1) self.label:SetTextColor(color_white) self.label:SetText("Label") self.label:SetContentAlignment(5) self.label:Dock(LEFT) self.label:DockMargin(0, 0, 4, 0) self.label:SizeToContents() self.label.Paint = function(this, width, height) derma.SkinFunc("PaintListRow", this, width, height) end self.text = self:Add("DLabel") self.text:SetFont("ixMediumLightFont") self.text:SetTextColor(color_white) self.text:SetText("Text") self.text:SetTextInset(8, 0) self.text:Dock(FILL) self.text:DockMargin(4, 0, 0, 0) self.text:SizeToContents() self.text.Paint = function(this, width, height) derma.SkinFunc("PaintListRow", this, width, height) end self:DockMargin(0, 0, 0, 8) self.list = {} self.minWidth = 100 end function PANEL:SetRightPanel(panel) self.text:Remove() self.text = self:Add(panel) self.text:Dock(FILL) self.text:DockMargin(8, 4, 4, 4) self.text:SizeToContents() end function PANEL:SetList(list, bNoAdd) if (!bNoAdd) then list[#list + 1] = self end self.list = list end function PANEL:UpdateLabelWidths() local maxWidth = self.label:GetWide() for i = 1, #self.list do maxWidth = math.max(self.list[i]:GetLabelWidth(), maxWidth) end maxWidth = math.max(self.minWidth, maxWidth) for i = 1, #self.list do self.list[i]:SetLabelWidth(maxWidth) end end function PANEL:SetLabelColor(color) self.label:SetTextColor(color) end function PANEL:SetTextColor(color) self.text:SetTextColor(color) end function PANEL:SetLabelText(text) self.label:SetText(text) self.label:SizeToContents() self:UpdateLabelWidths() end function PANEL:SetText(text) self.text:SetText(text) self.text:SizeToContents() end function PANEL:SetLabelWidth(width) self.label:SetWide(width) end function PANEL:GetLabelWidth(bWithoutMargin) if (!bWithoutMargin) then return self.label:GetWide() end local left, _, right, _ = self.label:GetDockMargin() return self.label:GetWide() + left + right end function PANEL:SizeToContents() self:SetTall(math.max(self.label:GetTall(), self.text:GetTall()) + 8) end vgui.Register("ixListRow", PANEL, "Panel") -- alternative checkbox DEFINE_BASECLASS("EditablePanel") PANEL = {} AccessorFunc(PANEL, "enabledText", "EnabledText", FORCE_STRING) AccessorFunc(PANEL, "disabledText", "DisabledText", FORCE_STRING) AccessorFunc(PANEL, "font", "Font", FORCE_STRING) AccessorFunc(PANEL, "bChecked", "Checked", FORCE_BOOL) AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER) PANEL.GetValue = PANEL.GetChecked function PANEL:Init() self:SetMouseInputEnabled(true) self:SetCursor("hand") self.enabledText = L("yes"):utf8upper() self.disabledText = L("no"):utf8upper() self.font = "ixMenuButtonFont" self.animationTime = 0.5 self.bChecked = false self.labelPadding = 8 self.animationOffset = 0 self:SizeToContents() end function PANEL:SizeToContents() BaseClass.SizeToContents(self) surface.SetFont(self.font) self:SetWide(math.max(surface.GetTextSize(self.enabledText), surface.GetTextSize(self.disabledText)) + self.labelPadding) end -- can be overidden to change audio params function PANEL:GetAudioFeedback() return "weapons/ar2/ar2_empty.wav", 75, self.bChecked and 150 or 125, 0.25 end function PANEL:EmitFeedback() LocalPlayer():EmitSound(self:GetAudioFeedback()) end function PANEL:SetChecked(bChecked, bInstant) self.bChecked = tobool(bChecked) self:CreateAnimation(bInstant and 0 or self.animationTime, { index = 1, target = { animationOffset = bChecked and 1 or 0 }, easing = "outElastic" }) if (!bInstant) then self:EmitFeedback() end end function PANEL:OnMousePressed(code) if (code == MOUSE_LEFT) then self:SetChecked(!self.bChecked) self:DoClick() end end function PANEL:DoClick() end function PANEL:Paint(width, height) surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) surface.DrawRect(0, 0, width, height) local offset = self.animationOffset surface.SetFont(self.font) local text = self.disabledText local textWidth, textHeight = surface.GetTextSize(text) local y = offset * -textHeight surface.SetTextColor(250, 60, 60, 255) surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) surface.DrawText(text) text = self.enabledText y = y + textHeight textWidth, textHeight = surface.GetTextSize(text) surface.SetTextColor(30, 250, 30, 255) surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) surface.DrawText(text) end vgui.Register("ixCheckBox", PANEL, "EditablePanel") -- alternative num slider PANEL = {} AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER) function PANEL:Init() self.labelPadding = 8 surface.SetFont("ixMenuButtonFont") local totalWidth = surface.GetTextSize("999") -- start off with 3 digit width self.label = self:Add("DLabel") self.label:Dock(RIGHT) self.label:SetWide(totalWidth + self.labelPadding) self.label:SetContentAlignment(5) self.label:SetFont("ixMenuButtonFont") self.label.Paint = function(panel, width, height) surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) surface.DrawRect(0, 0, width, height) end self.label.SizeToContents = function(panel) surface.SetFont(panel:GetFont()) local textWidth = surface.GetTextSize(panel:GetText()) if (textWidth > totalWidth) then panel:SetWide(textWidth + self.labelPadding) elseif (panel:GetWide() > totalWidth + self.labelPadding) then panel:SetWide(totalWidth + self.labelPadding) end end self.slider = self:Add("ixSlider") self.slider:Dock(FILL) self.slider:DockMargin(0, 0, 4, 0) self.slider.OnValueChanged = function(panel) self:OnValueChanged() end self.slider.OnValueUpdated = function(panel) self.label:SetText(string.format("%0." .. tostring(panel:GetDecimals()) .. "f", tostring(panel:GetValue()))) self.label:SizeToContents() self:OnValueUpdated() end end function PANEL:GetLabel() return self.label end function PANEL:GetSlider() return self.slider end function PANEL:SetValue(value, bNoNotify) value = tonumber(value) or self.slider:GetMin() self.slider:SetValue(value, bNoNotify) self.label:SetText(string.format("%0." .. tostring(self:GetDecimals()) .. "f", tostring(self.slider:GetValue()))) self.label:SizeToContents() end function PANEL:GetValue() return self.slider:GetValue() end function PANEL:GetFraction() return self.slider:GetFraction() end function PANEL:GetVisualFraction() return self.slider:GetVisualFraction() end function PANEL:SetMin(value) self.slider:SetMin(value) end function PANEL:SetMax(value) self.slider:SetMax(value) end function PANEL:GetMin() return self.slider:GetMin() end function PANEL:GetMax() return self.slider:GetMax() end function PANEL:SetDecimals(value) self.slider:SetDecimals(value) end function PANEL:GetDecimals() return self.slider:GetDecimals() end -- called when changed by user function PANEL:OnValueChanged() end -- called when changed while dragging bar function PANEL:OnValueUpdated() end vgui.Register("ixNumSlider", PANEL, "Panel") -- alternative slider PANEL = {} AccessorFunc(PANEL, "bDragging", "Dragging", FORCE_BOOL) AccessorFunc(PANEL, "min", "Min", FORCE_NUMBER) AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER) AccessorFunc(PANEL, "decimals", "Decimals", FORCE_NUMBER) function PANEL:Init() self.min = 0 self.max = 10 self.value = 0 self.visualValue = 0 self.decimals = 0 self:SetCursor("hand") end function PANEL:SetValue(value, bNoNotify) self.value = math.Clamp(math.Round(tonumber(value) or self.min, self.decimals), self.min, self.max) self:ValueUpdated(bNoNotify) if (!bNoNotify) then self:OnValueChanged() end end function PANEL:GetValue() return self.value end function PANEL:GetFraction() return math.Remap(self.value, self.min, self.max, 0, 1) end function PANEL:GetVisualFraction() return math.Remap(self.visualValue, self.min, self.max, 0, 1) end function PANEL:OnMousePressed(key) if (key == MOUSE_LEFT) then self.bDragging = true self:MouseCapture(true) self:OnCursorMoved(self:CursorPos()) end end function PANEL:OnMouseReleased(key) if (self.bDragging) then self:OnValueChanged() end self.bDragging = false self:MouseCapture(false) end function PANEL:OnCursorMoved(x, y) if (!self.bDragging) then return end x = math.Clamp(x, 0, self:GetWide()) local oldValue = self.value self.value = math.Clamp(math.Round( math.Remap(x / self:GetWide(), 0, 1, self.min, self.max), self.decimals ), self.min, self.max) self:CreateAnimation(0.5, { index = 1, target = {visualValue = self.value}, easing = "outQuint" }) if (self.value != oldValue) then self:ValueUpdated() end end function PANEL:OnValueChanged() end function PANEL:ValueUpdated(bNoNotify) self:CreateAnimation(bNoNotify and 0 or 0.5, { index = 1, target = {visualValue = self.value}, easing = "outQuint" }) if (!bNoNotify) then self:OnValueUpdated() end end function PANEL:OnValueUpdated() end function PANEL:Paint(width, height) derma.SkinFunc("PaintHelixSlider", self, width, height) end vgui.Register("ixSlider", PANEL, "EditablePanel") --- Alternative to DLabel that adds extra functionality. -- This panel is meant for drawing single-line text. It can add extra kerning (spaces between letters), and it can forcefully -- scale the text down to fit the current width, without cutting off any letters. Text scaling is most useful when docking this -- this panel without knowing what the width could be. For example, text scaling is used for the character name in the character -- status menu. -- local label = vgui.Create("ixLabel") -- label:SetText("hello world") -- label:SetFont("ixMenuButtonHugeFont") -- label:SetContentAlignment(5) -- label:SetTextColor(Color(255, 255, 255, 255)) -- label:SetBackgroundColor(Color(200, 30, 30, 255)) -- label:SetPadding(8) -- label:SetScaleWidth(true) -- label:SizeToContents() -- @panel ixLabel PANEL = {} --- Sets the text for this label to display. -- @realm client -- @string text Text to display -- @function SetText --- Returns the current text for this panel. -- @realm client -- @treturn string Current text -- @function GetText AccessorFunc(PANEL, "text", "Text", FORCE_STRING) --- Sets the color of the text to use when drawing. -- @realm client -- @color color New color to use -- @function SetTextColor --- Returns the current text color for this panel. -- @realm client -- @treturn color Current text color -- @function GetTextColor AccessorFunc(PANEL, "color", "TextColor") --- Sets the color of the background to draw behind the text. -- @realm client -- @color color New color to use -- @function SetBackgroundColor --- Returns the current background color for this panel. -- @realm client -- @treturn color Current background color -- @function GetBackgroundColor AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") --- Sets the spacing between each character of the text in pixels. Set to `0` to disable. Kerning is disabled by default. -- @realm client -- @number kerning How far apart to draw each letter -- @function SetKerning --- Returns the current kerning for this panel. -- @realm client -- @treturn number Current kerning -- @function GetKerning AccessorFunc(PANEL, "kerning", "Kerning", FORCE_NUMBER) --- Sets the font used to draw the text. -- @realm client -- @string font Name of the font to use -- @function SetFont --- Returns the current font for this panel. -- @realm client -- @treturn string Name of current font -- @function GetFont AccessorFunc(PANEL, "font", "Font", FORCE_STRING) --- Changes how the text is aligned when drawing. Valid content alignment values include numbers `1` through `9`. Each number's -- corresponding alignment is based on its position on a numpad. For example, `1` is bottom-left, `5` is centered, `9` is -- top-right, etc. -- @realm client -- @number alignment Alignment to use -- @function SetContentAlignment --- Returns the current content alignment for this panel. -- @realm client -- @treturn number Current content alignment -- @function GetContentAlignment AccessorFunc(PANEL, "contentAlignment", "ContentAlignment", FORCE_NUMBER) --- Whether or not to scale the width of the text down to fit the width of this panel, if needed. -- @realm client -- @bool bScale Whether or not to scale -- @function SetScaleWidth --- Returns whether or not this panel will scale its text down to fit its width. -- @realm client -- @treturn bool Whether or not this panel will scale its text -- @function GetScaleWidth AccessorFunc(PANEL, "bScaleWidth", "ScaleWidth", FORCE_BOOL) --- How much spacing to use around the text when its drawn. This uses uniform padding on the top, left, right, and bottom of -- this panel. -- @realm client -- @number padding Padding to use -- @function SetPadding --- Returns how much padding this panel has around its text. -- @realm client -- @treturn number Current padding -- @function GetPadding AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) function PANEL:Init() self.text = "" self.color = color_white self.backgroundColor = Color(255, 255, 255, 0) self.kerning = 0 self.font = "DermaDefault" self.scaledFont = "DermaDefault" self.contentAlignment = 5 self.bScaleWidth = false self.padding = 0 self.shadowDistance = 0 self.bCurrentlyScaling = false end function PANEL:SetText(text) self.text = tostring(text) end function PANEL:SetFont(font) self.font = font self.scaledFont = font end --- Sets the drop shadow to draw behind the text. -- @realm client -- @number distance How far away to draw the shadow in pixels. Set to `0` to disable -- @color[opt] color Color of the shadow. Defaults to a dimmed version of the text color function PANEL:SetDropShadow(distance, color) self.shadowDistance = distance or 1 self.shadowColor = color or ix.util.DimColor(self.color, 0.5) end PANEL.SetExpensiveShadow = PANEL.SetDropShadow -- aliasing for easier conversion from DLabels --- Returns the X and Y location of the text taking into account the text alignment and padding. -- @realm client -- @internal -- @number width Width of the panel -- @number height Height of the panel -- @number textWidth Width of the text -- @number textHeight Height of the text -- @treturn number X location to draw the text -- @treturn number Y location to draw the text function PANEL:CalculateAlignment(width, height, textWidth, textHeight) local alignment = self.contentAlignment local x, y if (self.bCurrentlyScaling) then -- if the text is currently being scaled down, then it's always centered x = width * 0.5 - textWidth * 0.5 else -- x alignment if (alignment == 7 or alignment == 4 or alignment == 1) then -- left x = self.padding elseif (alignment == 8 or alignment == 5 or alignment == 2) then -- center x = width * 0.5 - textWidth * 0.5 elseif (alignment == 9 or alignment == 6 or alignment == 3) then x = width - textWidth - self.padding end end -- y alignment if (alignment <= 3) then -- bottom y = height - textHeight - self.padding elseif (alignment <= 6) then -- center y = height * 0.5 - textHeight * 0.5 else -- top y = self.padding end return x, y end --- Draws the current text with the current kerning. -- @realm client -- @internal -- @number width Width of the panel -- @number height Height of the panel function PANEL:DrawKernedText(width, height) local contentWidth, contentHeight = self:GetContentSize() local x, y = self:CalculateAlignment(width, height, contentWidth, contentHeight) for i = 1, self.text:utf8len() do local character = self.text:utf8sub(i, i) local textWidth, _ = surface.GetTextSize(character) local kerning = i == 1 and 0 or self.kerning local shadowDistance = self.shadowDistance -- shadow if (self.shadowDistance > 0) then surface.SetTextColor(self.shadowColor) surface.SetTextPos(x + kerning + shadowDistance, y + shadowDistance) surface.DrawText(character) end -- character surface.SetTextColor(self.color) surface.SetTextPos(x + kerning, y) surface.DrawText(character) x = x + textWidth + kerning end end --- Draws the current text. -- @realm client -- @internal -- @number width Width of the panel -- @number height Height of the panel function PANEL:DrawText(width, height) local textWidth, textHeight = surface.GetTextSize(self.text) local x, y = self:CalculateAlignment(width, height, textWidth, textHeight) -- shadow if (self.shadowDistance > 0) then surface.SetTextColor(self.shadowColor) surface.SetTextPos(x + self.shadowDistance, y + self.shadowDistance) surface.DrawText(self.text) end -- text surface.SetTextColor(self.color) surface.SetTextPos(x, y) surface.DrawText(self.text) end function PANEL:Paint(width, height) surface.SetFont(self.font) surface.SetDrawColor(self.backgroundColor) surface.DrawRect(0, 0, width, height) if (self.bScaleWidth) then local contentWidth, contentHeight = self:GetContentSize() if (contentWidth > (width - self.padding * 2)) then local x, y = self:LocalToScreen(self:GetPos()) local scale = width / (contentWidth + self.padding * 2) local translation = Vector(x + width * 0.5, y - contentHeight * 0.5 + self.padding, 0) local matrix = Matrix() matrix:Translate(translation) matrix:Scale(Vector(scale, scale, 0)) matrix:Translate(-translation) cam.PushModelMatrix(matrix, true) render.PushFilterMin(TEXFILTER.ANISOTROPIC) DisableClipping(true) self.bCurrentlyScaling = true end end if (self.kerning > 0) then self:DrawKernedText(width, height) else self:DrawText(width, height) end if (self.bCurrentlyScaling) then DisableClipping(false) render.PopFilterMin() cam.PopModelMatrix() self.bCurrentlyScaling = false end end --- Returns the size of the text, taking into account the current kerning. -- @realm client -- @bool[opt=false] bCalculate Whether or not to recalculate the content size instead of using the cached copy -- @treturn number Width of the text -- @treturn number Height of the text function PANEL:GetContentSize(bCalculate) if (bCalculate or !self.contentSize) then surface.SetFont(self.font) if (self.kerning > 0) then local width = 0 for i = 1, self.text:utf8len() do local textWidth, _ = surface.GetTextSize(self.text:utf8sub(i, i)) width = width + textWidth + self.kerning end self.contentSize = {width, draw.GetFontHeight(self.font)} else self.contentSize = {surface.GetTextSize(self.text)} end end return self.contentSize[1], self.contentSize[2] end --- Sets the size of the panel to fit the content size with the current padding. The content size is recalculated when this -- method is called. -- @realm client function PANEL:SizeToContents() local contentWidth, contentHeight = self:GetContentSize(true) self:SetSize(contentWidth + self.padding * 2, contentHeight + self.padding * 2) end vgui.Register("ixLabel", PANEL, "Panel") -- text entry with icon DEFINE_BASECLASS("ixTextEntry") PANEL = {} AccessorFunc(PANEL, "icon", "Icon", FORCE_STRING) AccessorFunc(PANEL, "iconColor", "IconColor") function PANEL:Init() self:SetIcon("V") self:SetFont("ixSmallTitleFont") self.iconColor = Color(200, 200, 200, 160) end function PANEL:SetIcon(newIcon) surface.SetFont("ixSmallTitleIcons") self.iconWidth, self.iconHeight = surface.GetTextSize(newIcon) self.icon = newIcon self:DockMargin(self.iconWidth + 4, 0, 0, 8) end function PANEL:Paint(width, height) BaseClass.Paint(self, width, height) -- there's no inset for text entries so we'll have to get creative DisableClipping(true) surface.SetDrawColor(self:GetBackgroundColor()) surface.DrawRect(-self.iconWidth - 4, 0, self.iconWidth + 4, height) surface.SetFont("ixSmallTitleIcons") surface.SetTextColor(self.iconColor) surface.SetTextPos(-self.iconWidth - 2, 0) surface.DrawText(self:GetIcon()) DisableClipping(false) end vgui.Register("ixIconTextEntry", PANEL, "ixTextEntry") ================================================ FILE: gamemode/core/derma/cl_help.lua ================================================ local backgroundColor = Color(0, 0, 0, 66) local PANEL = {} AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER) function PANEL:Init() self:SetWide(180) self:Dock(LEFT) self.maxWidth = ScrW() * 0.2 end function PANEL:Paint(width, height) surface.SetDrawColor(backgroundColor) surface.DrawRect(0, 0, width, height) end function PANEL:SizeToContents() local width = 0 for _, v in ipairs(self:GetChildren()) do width = math.max(width, v:GetWide()) end self:SetSize(math.max(32, math.min(width, self.maxWidth)), self:GetParent():GetTall()) end vgui.Register("ixHelpMenuCategories", PANEL, "EditablePanel") -- help menu PANEL = {} function PANEL:Init() self:Dock(FILL) self.categories = {} self.categorySubpanels = {} self.categoryPanel = self:Add("ixHelpMenuCategories") self.canvasPanel = self:Add("EditablePanel") self.canvasPanel:Dock(FILL) self.idlePanel = self.canvasPanel:Add("Panel") self.idlePanel:Dock(FILL) self.idlePanel:DockMargin(8, 0, 0, 0) self.idlePanel.Paint = function(_, width, height) surface.SetDrawColor(backgroundColor) surface.DrawRect(0, 0, width, height) derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25) surface.SetFont("ixIntroSubtitleFont") local text = L("helix"):lower() local textWidth, textHeight = surface.GetTextSize(text) surface.SetTextColor(color_white) surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.75) surface.DrawText(text) surface.SetFont("ixMediumLightFont") text = L("helpIdle") local infoWidth, _ = surface.GetTextSize(text) surface.SetTextColor(color_white) surface.SetTextPos(width * 0.5 - infoWidth * 0.5, height * 0.5 + textHeight * 0.25) surface.DrawText(text) end local categories = {} hook.Run("PopulateHelpMenu", categories) for k, v in SortedPairs(categories) do if (!isstring(k)) then ErrorNoHalt("expected string for help menu key\n") continue elseif (!isfunction(v)) then ErrorNoHalt(string.format("expected function for help menu entry '%s'\n", k)) continue end self:AddCategory(k) self.categories[k] = v end self.categoryPanel:SizeToContents() if (ix.gui.lastHelpMenuTab) then self:OnCategorySelected(ix.gui.lastHelpMenuTab) end end function PANEL:AddCategory(name) local button = self.categoryPanel:Add("ixMenuButton") button:SetText(L(name)) button:SizeToContents() -- @todo don't hardcode this but it's the only panel that needs docking at the bottom so it'll do for now button:Dock(name == "credits" and BOTTOM or TOP) button.DoClick = function() self:OnCategorySelected(name) end local panel = self.canvasPanel:Add("DScrollPanel") panel:SetVisible(false) panel:Dock(FILL) panel:DockMargin(8, 0, 0, 0) panel:GetCanvas():DockPadding(8, 8, 8, 8) panel.Paint = function(_, width, height) surface.SetDrawColor(backgroundColor) surface.DrawRect(0, 0, width, height) end -- reverts functionality back to a standard panel in the case that a category will manage its own scrolling panel.DisableScrolling = function() panel:GetCanvas():SetVisible(false) panel:GetVBar():SetVisible(false) panel.OnChildAdded = function() end end self.categorySubpanels[name] = panel end function PANEL:OnCategorySelected(name) local panel = self.categorySubpanels[name] if (!IsValid(panel)) then return end if (!panel.bPopulated) then self.categories[name](panel) panel.bPopulated = true end if (IsValid(self.activeCategory)) then self.activeCategory:SetVisible(false) end panel:SetVisible(true) self.idlePanel:SetVisible(false) self.activeCategory = panel ix.gui.lastHelpMenuTab = name end vgui.Register("ixHelpMenu", PANEL, "EditablePanel") local function DrawHelix(width, height, color) -- luacheck: ignore 211 local segments = 76 local radius = math.min(width, height) * 0.375 surface.SetTexture(-1) for i = 1, math.ceil(segments) do local angle = math.rad((i / segments) * -360) local x = width * 0.5 + math.sin(angle + math.pi * 2) * radius local y = height * 0.5 + math.cos(angle + math.pi * 2) * radius local barOffset = math.sin(SysTime() + i * 0.5) local barHeight = barOffset * radius * 0.25 if (barOffset > 0) then surface.SetDrawColor(color) else surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) end surface.DrawTexturedRectRotated(x, y, 4, barHeight, math.deg(angle)) end end hook.Add("CreateMenuButtons", "ixHelpMenu", function(tabs) tabs["help"] = function(container) container:Add("ixHelpMenu") end end) hook.Add("PopulateHelpMenu", "ixHelpMenu", function(tabs) tabs["commands"] = function(container) -- info text local info = container:Add("DLabel") info:SetFont("ixSmallFont") info:SetText(L("helpCommands")) info:SetContentAlignment(5) info:SetTextColor(color_white) info:SetExpensiveShadow(1, color_black) info:Dock(TOP) info:DockMargin(0, 0, 0, 8) info:SizeToContents() info:SetTall(info:GetTall() + 16) info.Paint = function(_, width, height) surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160)) surface.DrawRect(0, 0, width, height) end -- commands for uniqueID, command in SortedPairs(ix.command.list) do if (command.OnCheckAccess and !command:OnCheckAccess(LocalPlayer())) then continue end local bIsAlias = false local aliasText = "" -- we want to show aliases in the same entry for better readability if (command.alias) then local alias = istable(command.alias) and command.alias or {command.alias} for _, v in ipairs(alias) do if (v:lower() == uniqueID) then bIsAlias = true break end aliasText = aliasText .. ", /" .. v end if (bIsAlias) then continue end end -- command name local title = container:Add("DLabel") title:SetFont("ixMediumLightFont") title:SetText("/" .. command.name .. aliasText) title:Dock(TOP) title:SetTextColor(ix.config.Get("color")) title:SetExpensiveShadow(1, color_black) title:SizeToContents() -- syntax local syntaxText = command.syntax local syntax if (syntaxText != "" and syntaxText != "[none]") then syntax = container:Add("DLabel") syntax:SetFont("ixMediumLightFont") syntax:SetText(syntaxText) syntax:Dock(TOP) syntax:SetTextColor(color_white) syntax:SetExpensiveShadow(1, color_black) syntax:SetWrap(true) syntax:SetAutoStretchVertical(true) syntax:SizeToContents() end -- description local descriptionText = command:GetDescription() if (descriptionText != "") then local description = container:Add("DLabel") description:SetFont("ixSmallFont") description:SetText(descriptionText) description:Dock(TOP) description:SetTextColor(color_white) description:SetExpensiveShadow(1, color_black) description:SetWrap(true) description:SetAutoStretchVertical(true) description:SizeToContents() description:DockMargin(0, 0, 0, 8) elseif (syntax) then syntax:DockMargin(0, 0, 0, 8) else title:DockMargin(0, 0, 0, 8) end end end tabs["flags"] = function(container) -- info text local info = container:Add("DLabel") info:SetFont("ixSmallFont") info:SetText(L("helpFlags")) info:SetContentAlignment(5) info:SetTextColor(color_white) info:SetExpensiveShadow(1, color_black) info:Dock(TOP) info:DockMargin(0, 0, 0, 8) info:SizeToContents() info:SetTall(info:GetTall() + 16) info.Paint = function(_, width, height) surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160)) surface.DrawRect(0, 0, width, height) end -- flags for k, v in SortedPairs(ix.flag.list) do local background = ColorAlpha( LocalPlayer():GetCharacter():HasFlags(k) and derma.GetColor("Success", info) or derma.GetColor("Error", info), 88 ) local panel = container:Add("Panel") panel:Dock(TOP) panel:DockMargin(0, 0, 0, 8) panel:DockPadding(4, 4, 4, 4) panel.Paint = function(_, width, height) derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, background) end local flag = panel:Add("DLabel") flag:SetFont("ixMonoMediumFont") flag:SetText(string.format("[%s]", k)) flag:Dock(LEFT) flag:SetTextColor(color_white) flag:SetExpensiveShadow(1, color_black) flag:SetTextInset(4, 0) flag:SizeToContents() flag:SetTall(flag:GetTall() + 8) local description = panel:Add("DLabel") description:SetFont("ixMediumLightFont") description:SetText(v.description) description:Dock(FILL) description:SetTextColor(color_white) description:SetExpensiveShadow(1, color_black) description:SetTextInset(8, 0) description:SizeToContents() description:SetTall(description:GetTall() + 8) panel:SizeToChildren(false, true) end end tabs["plugins"] = function(container) for _, v in SortedPairsByMemberValue(ix.plugin.list, "name") do -- name local title = container:Add("DLabel") title:SetFont("ixMediumLightFont") title:SetText(v.name or "Unknown") title:Dock(TOP) title:SetTextColor(ix.config.Get("color")) title:SetExpensiveShadow(1, color_black) title:SizeToContents() -- author local author = container:Add("DLabel") author:SetFont("ixSmallFont") author:SetText(string.format("%s: %s", L("author"), v.author)) author:Dock(TOP) author:SetTextColor(color_white) author:SetExpensiveShadow(1, color_black) author:SetWrap(true) author:SetAutoStretchVertical(true) author:SizeToContents() -- description local descriptionText = v.description if (descriptionText != "") then local description = container:Add("DLabel") description:SetFont("ixSmallFont") description:SetText(descriptionText) description:Dock(TOP) description:SetTextColor(color_white) description:SetExpensiveShadow(1, color_black) description:SetWrap(true) description:SetAutoStretchVertical(true) description:SizeToContents() description:DockMargin(0, 0, 0, 8) else author:DockMargin(0, 0, 0, 8) end end end end) ================================================ FILE: gamemode/core/derma/cl_information.lua ================================================ local PANEL = {} function PANEL:Init() local parent = self:GetParent() self:SetSize(parent:GetWide() * 0.6, parent:GetTall()) self:Dock(RIGHT) self:DockMargin(0, ScrH() * 0.05, 0, 0) self.VBar:SetWide(0) -- entry setup local suppress = {} hook.Run("CanCreateCharacterInfo", suppress) if (!suppress.time) then local format = ix.option.Get("24hourTime", false) and "%A, %B %d, %Y. %H:%M" or "%A, %B %d, %Y. %I:%M %p" self.time = self:Add("DLabel") self.time:SetFont("ixMediumFont") self.time:SetTall(28) self.time:SetContentAlignment(5) self.time:Dock(TOP) self.time:SetTextColor(color_white) self.time:SetExpensiveShadow(1, Color(0, 0, 0, 150)) self.time:DockMargin(0, 0, 0, 32) self.time:SetText(ix.date.GetFormatted(format)) self.time.Think = function(this) if ((this.nextTime or 0) < CurTime()) then this:SetText(ix.date.GetFormatted(format)) this.nextTime = CurTime() + 0.5 end end end if (!suppress.name) then self.name = self:Add("ixLabel") self.name:Dock(TOP) self.name:DockMargin(0, 0, 0, 8) self.name:SetFont("ixMenuButtonHugeFont") self.name:SetContentAlignment(5) self.name:SetTextColor(color_white) self.name:SetPadding(8) self.name:SetScaleWidth(true) end if (!suppress.description) then self.description = self:Add("DLabel") self.description:Dock(TOP) self.description:DockMargin(0, 0, 0, 8) self.description:SetFont("ixMenuButtonFont") self.description:SetTextColor(color_white) self.description:SetContentAlignment(5) self.description:SetMouseInputEnabled(true) self.description:SetCursor("hand") self.description.Paint = function(this, width, height) surface.SetDrawColor(0, 0, 0, 150) surface.DrawRect(0, 0, width, height) end self.description.OnMousePressed = function(this, code) if (code == MOUSE_LEFT) then ix.command.Send("CharDesc") if (IsValid(ix.gui.menu)) then ix.gui.menu:Remove() end end end self.description.SizeToContents = function(this) if (this.bWrap) then -- sizing contents after initial wrapping does weird things so we'll just ignore (lol) return end local width, height = this:GetContentSize() if (width > self:GetWide()) then this:SetWide(self:GetWide()) this:SetTextInset(16, 8) this:SetWrap(true) this:SizeToContentsY() this:SetTall(this:GetTall() + 16) -- eh -- wrapping doesn't like middle alignment so we'll do top-center self.description:SetContentAlignment(8) this.bWrap = true else this:SetSize(width + 16, height + 16) end end end if (!suppress.characterInfo) then self.characterInfo = self:Add("Panel") self.characterInfo.list = {} self.characterInfo:Dock(TOP) -- no dock margin because this is handled by ixListRow self.characterInfo.SizeToContents = function(this) local height = 0 for _, v in ipairs(this:GetChildren()) do if (IsValid(v) and v:IsVisible()) then local _, top, _, bottom = v:GetDockMargin() height = height + v:GetTall() + top + bottom end end this:SetTall(height) end if (!suppress.faction) then self.faction = self.characterInfo:Add("ixListRow") self.faction:SetList(self.characterInfo.list) self.faction:Dock(TOP) end if (!suppress.class) then self.class = self.characterInfo:Add("ixListRow") self.class:SetList(self.characterInfo.list) self.class:Dock(TOP) end if (!suppress.money) then self.money = self.characterInfo:Add("ixListRow") self.money:SetList(self.characterInfo.list) self.money:Dock(TOP) self.money:SizeToContents() end hook.Run("CreateCharacterInfo", self.characterInfo) self.characterInfo:SizeToContents() end -- no need to update since we aren't showing the attributes panel if (!suppress.attributes) then local character = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() if (character) then self.attributes = self:Add("ixCategoryPanel") self.attributes:SetText(L("attributes")) self.attributes:Dock(TOP) self.attributes:DockMargin(0, 0, 0, 8) local boost = character:GetBoosts() local bFirst = true for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do local attributeBoost = 0 if (boost[k]) then for _, bValue in pairs(boost[k]) do attributeBoost = attributeBoost + bValue end end local bar = self.attributes:Add("ixAttributeBar") bar:Dock(TOP) if (!bFirst) then bar:DockMargin(0, 3, 0, 0) else bFirst = false end local value = character:GetAttribute(k, 0) if (attributeBoost) then bar:SetValue(value - attributeBoost or 0) else bar:SetValue(value) end local maximum = v.maxValue or ix.config.Get("maxAttributes", 100) bar:SetMax(maximum) bar:SetReadOnly() bar:SetText(Format("%s [%.1f/%.1f] (%.1f%%)", L(v.name), value, maximum, value / maximum * 100)) if (attributeBoost) then bar:SetBoost(attributeBoost) end end self.attributes:SizeToContents() end end hook.Run("CreateCharacterInfoCategory", self) end function PANEL:Update(character) if (!character) then return end local faction = ix.faction.indices[character:GetFaction()] local class = ix.class.list[character:GetClass()] if (self.name) then self.name:SetText(character:GetName()) if (faction) then self.name.backgroundColor = ColorAlpha(faction.color, 150) or Color(0, 0, 0, 150) end self.name:SizeToContents() end if (self.description) then self.description:SetText(character:GetDescription()) self.description:SizeToContents() end if (self.faction) then self.faction:SetLabelText(L("faction")) self.faction:SetText(L(faction.name)) self.faction:SizeToContents() end if (self.class) then -- don't show class label if the class is the same name as the faction if (class and class.name != faction.name) then self.class:SetLabelText(L("class")) self.class:SetText(L(class.name)) self.class:SizeToContents() else self.class:SetVisible(false) end end if (self.money) then self.money:SetLabelText(L("money")) self.money:SetText(ix.currency.Get(character:GetMoney())) self.money:SizeToContents() end hook.Run("UpdateCharacterInfo", self.characterInfo, character) self.characterInfo:SizeToContents() hook.Run("UpdateCharacterInfoCategory", self, character) end function PANEL:OnSubpanelRightClick() properties.OpenEntityMenu(LocalPlayer()) end vgui.Register("ixCharacterInfo", PANEL, "DScrollPanel") hook.Add("CreateMenuButtons", "ixCharInfo", function(tabs) tabs["you"] = { bHideBackground = true, buttonColor = team.GetColor(LocalPlayer():Team()), Create = function(info, container) container.infoPanel = container:Add("ixCharacterInfo") container.OnMouseReleased = function(this, key) if (key == MOUSE_RIGHT) then this.infoPanel:OnSubpanelRightClick() end end end, OnSelected = function(info, container) container.infoPanel:Update(LocalPlayer():GetCharacter()) ix.gui.menu:SetCharacterOverview(true) end, OnDeselected = function(info, container) ix.gui.menu:SetCharacterOverview(false) end } end) ================================================ FILE: gamemode/core/derma/cl_intro.lua ================================================ local waveSegments = 32 local helixSegments = 76 local helixHeight = 64 local backgroundColor = Color(115, 53, 142) local dimColor = Color(165, 134, 179) DEFINE_BASECLASS("EditablePanel") local PANEL = {} function PANEL:Init() if (IsValid(ix.gui.intro)) then ix.gui.intro:Remove() end ix.gui.intro = self self:SetSize(ScrW(), ScrH()) self:SetPos(0, 0) self:SetZPos(99999) self:MakePopup() -- animation parameters self.bBackground = true self.volume = 1 self.sunbeamOffset = 0 self.textOne = 0 self.textTwo = 0 self.kickTarget = 0 self.helix = 0 self.helixAlpha = 0 self.continueText = 0 self.pulse = 0 self.waves = { {1.1, 0}, {1.1, math.pi}, {1.1, math.pi * 1.6}, {1.1, math.pi * 0.5} } end -- @todo h a c k function PANEL:Think() if (IsValid(LocalPlayer())) then self:BeginIntro() self.Think = nil end end function PANEL:BeginIntro() -- something could have errored on startup and invalidated all options, so we'll be extra careful with setting the option -- because if it errors here, the sound will play each tick and proceed to hurt ears local bLoaded = false if (ix and ix.option and ix.option.Set) then local bSuccess, _ = pcall(ix.option.Set, "showIntro", false) bLoaded = bSuccess end if (!bLoaded) then self:Remove() if (ix and ix.gui and IsValid(ix.gui.characterMenu)) then ix.gui.characterMenu:Remove() end ErrorNoHalt( "[Helix] Something has errored and prevented the framework from loading correctly - check your console for errors!\n") return end self:MoveToFront() self:RequestFocus() sound.PlayFile("sound/buttons/combine_button2.wav", "", function() timer.Create("ixIntroStart", 2, 1, function() sound.PlayFile("sound/helix/intro.mp3", "", function(channel, status, error) if (IsValid(channel)) then channel:SetVolume(self.volume) self.channel = channel end self:BeginAnimation() end) end) end) end function PANEL:AnimateWaves(target, bReverse) for i = bReverse and #self.waves or 1, bReverse and 1 or #self.waves, bReverse and -1 or 1 do local animation = self:CreateAnimation(2, { index = 20 + (bReverse and (#self.waves - i) or i), bAutoFire = false, target = { waves = { [i] = {target} } }, easing = bReverse and "inQuart" or "outQuint" }) timer.Simple((bReverse and (#self.waves - i) or i) * 0.1, function() if (IsValid(self) and animation) then animation:Fire() end end) -- return last animation that plays if ((bReverse and i == 1) or (!bReverse and i == #self.waves)) then return animation end end end function PANEL:BeginAnimation() self:CreateAnimation(2, { target = {textOne = 1}, easing = "inQuint", bIgnoreConfig = true }) :CreateAnimation(2, { target = {textOne = 0}, easing = "inQuint", bIgnoreConfig = true }) :CreateAnimation(2, { target = {textTwo = 1}, easing = "inQuint", bIgnoreConfig = true, OnComplete = function(animation, panel) self:AnimateWaves(0) end }) :CreateAnimation(2, { target = {textTwo = 0}, easing = "inQuint", bIgnoreConfig = true }) :CreateAnimation(4, { target = {sunbeamOffset = 1}, bIgnoreConfig = true, OnComplete = function() self:CreateAnimation(2,{ target = {helixAlpha = 1}, easing = "inCubic" }) end }) :CreateAnimation(2, { target = {helix = 1}, easing = "outQuart", bIgnoreConfig = true }) :CreateAnimation(2, { target = {continueText = 1}, easing = "linear", bIgnoreConfig = true }) end function PANEL:PaintCurve(y, width, offset, scale) offset = offset or 1 scale = scale or 32 local points = { [1] = { x = 0, y = ScrH() } } for i = 0, waveSegments do local angle = math.rad((i / waveSegments) * -360) points[#points + 1] = { x = (width / waveSegments) * i, y = y + (math.sin(angle * 0.5 + offset) - 1) * scale } end points[#points + 1] = { x = width, y = ScrH() } draw.NoTexture() surface.DrawPoly(points) end function PANEL:Paint(width, height) local time = SysTime() local text = L("helix"):lower() local centerY = height * self.waves[#self.waves][1] + height * 0.5 local sunbeamOffsetEasing = math.sin(math.pi * self.sunbeamOffset) local textWidth, textHeight local fft -- background if (self.bBackground) then surface.SetDrawColor(0, 0, 0, 255) surface.DrawRect(0, 0, width, height) end if (self.sunbeamOffset == 1) then fft = {} if (IsValid(self.channel)) then self.channel:FFT(fft, FFT_2048) local kick = (fft[4] or 0) * 8192 self.kickTarget = math.Approach(self.kickTarget, kick, 8 * math.abs(kick - self.kickTarget) * FrameTime()) end end -- waves for i = 1, #self.waves do local wave = self.waves[i] local ratio = i / #self.waves local color = Color( backgroundColor.r * ratio, backgroundColor.g * ratio, backgroundColor.b * ratio, self.bBackground and 255 or (ratio * 320) ) surface.SetDrawColor(color) self:PaintCurve(height * wave[1], width, wave[2]) end -- helix if (self.helix > 0) then local alpha = self.helixAlpha * 255 derma.SkinFunc("DrawHelixCurved", width * 0.5, centerY, math.min(ScreenScale(72), 128) * 2, -- font sizes are clamped to 128 helixSegments * self.helix, helixHeight, self.helix, ColorAlpha(color_white, alpha), ColorAlpha(dimColor, alpha) ) end -- title text glow surface.SetTextColor(255, 255, 255, self.sunbeamOffset == 1 and self.kickTarget or sunbeamOffsetEasing * 255 ) surface.SetFont("ixIntroTitleBlurFont") local logoTextWidth, logoTextHeight = surface.GetTextSize(text) surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5) surface.DrawText(text) -- title text surface.SetTextColor(255, 255, 255, self.sunbeamOffset * 255) surface.SetFont("ixIntroTitleFont") logoTextWidth, logoTextHeight = surface.GetTextSize(text) surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5) surface.DrawText(text) -- text one surface.SetFont("ixIntroSubtitleFont") text = L("introTextOne"):lower() textWidth = surface.GetTextSize(text) surface.SetTextColor(255, 255, 255, self.textOne * 255) surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66) surface.DrawText(text) -- text two text = L("introTextTwo", Schema.author or "nebulous"):lower() textWidth = surface.GetTextSize(text) surface.SetTextColor(255, 255, 255, self.textTwo * 255) surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66) surface.DrawText(text) -- continue text surface.SetFont("ixIntroSmallFont") text = L("introContinue"):lower() textWidth, textHeight = surface.GetTextSize(text) if (self.continueText == 1) then self.pulse = self.pulse + 6 * FrameTime() if (self.pulse >= 360) then self.pulse = 0 end end surface.SetTextColor(255, 255, 255, self.continueText * 255 - (math.sin(self.pulse) * 100), 0) surface.SetTextPos(width * 0.5 - textWidth * 0.5, centerY * 2 - textHeight * 2) surface.DrawText(text) -- sunbeams if (self.sunbeamOffset > 0 and self.sunbeamOffset != 1) then DrawSunbeams(0.25, sunbeamOffsetEasing * 0.1, 0.02, (((width * 0.5 - logoTextWidth * 0.5) - 32) / width) + ((logoTextWidth + 64) / width) * self.sunbeamOffset, 0.5 + math.sin(time * 2) * 0.01 ) end end function PANEL:OnKeyCodePressed(key) if (key == KEY_SPACE and self.continueText > 0.25) then self:Remove() end end function PANEL:OnRemove() timer.Remove("ixIntroStart") if (IsValid(self.channel)) then self.channel:Stop() end if (IsValid(ix.gui.characterMenu)) then ix.gui.characterMenu:PlayMusic() end end function PANEL:Remove(bForce) if (bForce) then BaseClass.Remove(self) return end if (self.bClosing) then return end self.bClosing = true self.bBackground = nil -- waves local animation = self:AnimateWaves(1.1, true) animation.OnComplete = function(anim, panel) panel:SetMouseInputEnabled(false) panel:SetKeyboardInputEnabled(false) end -- audio self:CreateAnimation(4.5, { index = 1, target = {volume = 0}, Think = function(anim, panel) if (IsValid(panel.channel)) then panel.channel:SetVolume(panel.volume) end end, OnComplete = function() timer.Simple(0, function() BaseClass.Remove(self) end) end }) end vgui.Register("ixIntro", PANEL, "EditablePanel") ================================================ FILE: gamemode/core/derma/cl_inventory.lua ================================================ local RECEIVER_NAME = "ixInventoryItem" -- The queue for the rendered icons. ICON_RENDER_QUEUE = ICON_RENDER_QUEUE or {} -- To make making inventory variant, This must be followed up. local function RenderNewIcon(panel, itemTable) local model = itemTable:GetModel() -- re-render icons if ((itemTable.iconCam and !ICON_RENDER_QUEUE[string.lower(model)]) or itemTable.forceRender) then local iconCam = itemTable.iconCam iconCam = { cam_pos = iconCam.pos, cam_ang = iconCam.ang, cam_fov = iconCam.fov, } ICON_RENDER_QUEUE[string.lower(model)] = true panel.Icon:RebuildSpawnIconEx( iconCam ) end end local function InventoryAction(action, itemID, invID, data) net.Start("ixInventoryAction") net.WriteString(action) net.WriteUInt(itemID, 32) net.WriteUInt(invID, 32) net.WriteTable(data or {}) net.SendToServer() end local PANEL = {} AccessorFunc(PANEL, "itemTable", "ItemTable") AccessorFunc(PANEL, "inventoryID", "InventoryID") function PANEL:Init() self:Droppable(RECEIVER_NAME) end function PANEL:OnMousePressed(code) if (code == MOUSE_LEFT and self:IsDraggable()) then self:MouseCapture(true) self:DragMousePress(code) self.clickX, self.clickY = input.GetCursorPos() elseif (code == MOUSE_RIGHT and self.DoRightClick) then self:DoRightClick() end end function PANEL:OnMouseReleased(code) -- move the item into the world if we're dropping on something that doesn't handle inventory item drops if (!dragndrop.m_ReceiverSlot or dragndrop.m_ReceiverSlot.Name != RECEIVER_NAME) then self:OnDrop(dragndrop.IsDragging()) end self:DragMouseRelease(code) self:SetZPos(99) self:MouseCapture(false) end function PANEL:DoRightClick() local itemTable = self.itemTable local inventory = self.inventoryID if (itemTable and inventory) then itemTable.player = LocalPlayer() local menu = DermaMenu() local override = hook.Run("CreateItemInteractionMenu", self, menu, itemTable) if (override == true) then if (menu.Remove) then menu:Remove() end return end for k, v in SortedPairs(itemTable.functions) do if (k == "drop" or k == "combine" or (v.OnCanRun and v.OnCanRun(itemTable) == false)) then continue end -- is Multi-Option Function if (v.isMulti) then local subMenu, subMenuOption = menu:AddSubMenu(L(v.name or k), function() itemTable.player = LocalPlayer() local send = true if (v.OnClick) then send = v.OnClick(itemTable) end if (v.sound) then surface.PlaySound(v.sound) end if (send != false) then InventoryAction(k, itemTable.id, inventory) end itemTable.player = nil end) subMenuOption:SetImage(v.icon or "icon16/brick.png") if (v.multiOptions) then local options = isfunction(v.multiOptions) and v.multiOptions(itemTable, LocalPlayer()) or v.multiOptions for _, sub in pairs(options) do subMenu:AddOption(L(sub.name or "subOption"), function() itemTable.player = LocalPlayer() local send = true if (sub.OnClick) then send = sub.OnClick(itemTable) end if (sub.sound) then surface.PlaySound(sub.sound) end if (send != false) then InventoryAction(k, itemTable.id, inventory, sub.data) end itemTable.player = nil end) end end else menu:AddOption(L(v.name or k), function() itemTable.player = LocalPlayer() local send = true if (v.OnClick) then send = v.OnClick(itemTable) end if (v.sound) then surface.PlaySound(v.sound) end if (send != false) then InventoryAction(k, itemTable.id, inventory) end itemTable.player = nil end):SetImage(v.icon or "icon16/brick.png") end end -- we want drop to show up as the last option local info = itemTable.functions.drop if (info and info.OnCanRun and info.OnCanRun(itemTable) != false) then menu:AddOption(L(info.name or "drop"), function() itemTable.player = LocalPlayer() local send = true if (info.OnClick) then send = info.OnClick(itemTable) end if (info.sound) then surface.PlaySound(info.sound) end if (send != false) then InventoryAction("drop", itemTable.id, inventory) end itemTable.player = nil end):SetImage(info.icon or "icon16/brick.png") end menu:Open() itemTable.player = nil end end function PANEL:OnDrop(bDragging, inventoryPanel, inventory, gridX, gridY) local item = self.itemTable if (!item or !bDragging) then return end if (!IsValid(inventoryPanel)) then local inventoryID = self.inventoryID if (inventoryID) then InventoryAction("drop", item.id, inventoryID, {}) end elseif (inventoryPanel:IsAllEmpty(gridX, gridY, item.width, item.height, self)) then local oldX, oldY = self.gridX, self.gridY if (oldX != gridX or oldY != gridY or self.inventoryID != inventoryPanel.invID) then self:Move(gridX, gridY, inventoryPanel) end elseif (inventoryPanel.combineItem) then local combineItem = inventoryPanel.combineItem local inventoryID = combineItem.invID if (inventoryID) then combineItem.player = LocalPlayer() if (combineItem.functions.combine.sound) then surface.PlaySound(combineItem.functions.combine.sound) end InventoryAction("combine", combineItem.id, inventoryID, {item.id}) combineItem.player = nil end end end function PANEL:Move(newX, newY, givenInventory, bNoSend) local iconSize = givenInventory.iconSize local oldX, oldY = self.gridX, self.gridY local oldParent = self:GetParent() if (givenInventory:OnTransfer(oldX, oldY, newX, newY, oldParent, bNoSend) == false) then return end local x = (newX - 1) * iconSize + 4 local y = (newY - 1) * iconSize + givenInventory:GetPadding(2) self.gridX = newX self.gridY = newY self:SetParent(givenInventory) self:SetPos(x, y) if (self.slots) then for _, v in ipairs(self.slots) do if (IsValid(v) and v.item == self) then v.item = nil end end end self.slots = {} for currentX = 1, self.gridW do for currentY = 1, self.gridH do local slot = givenInventory.slots[self.gridX + currentX - 1][self.gridY + currentY - 1] slot.item = self self.slots[#self.slots + 1] = slot end end end function PANEL:PaintOver(width, height) local itemTable = self.itemTable if (itemTable and itemTable.PaintOver) then itemTable.PaintOver(self, itemTable, width, height) end end function PANEL:ExtraPaint(width, height) end function PANEL:Paint(width, height) surface.SetDrawColor(0, 0, 0, 85) surface.DrawRect(2, 2, width - 4, height - 4) self:ExtraPaint(width, height) end vgui.Register("ixItemIcon", PANEL, "SpawnIcon") PANEL = {} DEFINE_BASECLASS("DFrame") AccessorFunc(PANEL, "iconSize", "IconSize", FORCE_NUMBER) AccessorFunc(PANEL, "bHighlighted", "Highlighted", FORCE_BOOL) function PANEL:Init() self:SetIconSize(64) self:ShowCloseButton(false) self:SetDraggable(true) self:SetSizable(true) self:SetTitle(L"inv") self:Receiver(RECEIVER_NAME, self.ReceiveDrop) self.btnMinim:SetVisible(false) self.btnMinim:SetMouseInputEnabled(false) self.btnMaxim:SetVisible(false) self.btnMaxim:SetMouseInputEnabled(false) self.panels = {} end function PANEL:GetPadding(index) return select(index, self:GetDockPadding()) end function PANEL:SetTitle(text) if (text == nil) then self.oldPadding = {self:GetDockPadding()} self.lblTitle:SetText("") self.lblTitle:SetVisible(false) self:DockPadding(5, 5, 5, 5) else if (self.oldPadding) then self:DockPadding(unpack(self.oldPadding)) self.oldPadding = nil end BaseClass.SetTitle(self, text) end end function PANEL:FitParent(invWidth, invHeight) local parent = self:GetParent() if (!IsValid(parent)) then return end local width, height = parent:GetSize() local padding = 4 local iconSize if (invWidth > invHeight) then iconSize = (width - padding * 2) / invWidth elseif (invHeight > invWidth) then iconSize = (height - padding * 2) / invHeight else -- we use height because the titlebar will make it more tall than it is wide iconSize = (height - padding * 2) / invHeight - 4 end self:SetSize(iconSize * invWidth + padding * 2, iconSize * invHeight + padding * 2) self:SetIconSize(iconSize) end function PANEL:OnRemove() if (self.childPanels) then for _, v in ipairs(self.childPanels) do if (v != self) then v:Remove() end end end end function PANEL:ViewOnly() self.viewOnly = true for _, icon in pairs(self.panels) do icon.OnMousePressed = nil icon.OnMouseReleased = nil icon.doRightClick = nil end end function PANEL:SetInventory(inventory, bFitParent) if (inventory.slots) then local invWidth, invHeight = inventory:GetSize() self.invID = inventory:GetID() if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels and inventory != LocalPlayer():GetCharacter():GetInventory()) then self:SetIconSize(ix.gui.inv1:GetIconSize()) self:SetPaintedManually(true) self.bNoBackgroundBlur = true ix.gui.inv1.childPanels[#ix.gui.inv1.childPanels + 1] = self elseif (bFitParent) then self:FitParent(invWidth, invHeight) else self:SetSize(self.iconSize, self.iconSize) end self:SetGridSize(invWidth, invHeight) for x, items in pairs(inventory.slots) do for y, data in pairs(items) do if (!data.id) then continue end local item = ix.item.instances[data.id] if (item and !IsValid(self.panels[item.id])) then local icon = self:AddIcon(item:GetModel() or "models/props_junk/popcan01a.mdl", x, y, item.width, item.height, item:GetSkin()) if (IsValid(icon)) then icon:SetHelixTooltip(function(tooltip) ix.hud.PopulateItemTooltip(tooltip, item) end) self.panels[item.id] = icon end end end end end end function PANEL:SetGridSize(w, h) local iconSize = self.iconSize local newWidth = w * iconSize + 8 local newHeight = h * iconSize + self:GetPadding(2) + self:GetPadding(4) self.gridW = w self.gridH = h self:SetSize(newWidth, newHeight) self:SetMinWidth(newWidth) self:SetMinHeight(newHeight) self:BuildSlots() end function PANEL:PerformLayout(width, height) BaseClass.PerformLayout(self, width, height) if (self.Sizing and self.gridW and self.gridH) then local newWidth = (width - 8) / self.gridW local newHeight = (height - self:GetPadding(2) + self:GetPadding(4)) / self.gridH self:SetIconSize((newWidth + newHeight) / 2) self:RebuildItems() end end function PANEL:BuildSlots() local iconSize = self.iconSize self.slots = self.slots or {} for _, v in ipairs(self.slots) do for _, v2 in ipairs(v) do v2:Remove() end end self.slots = {} for x = 1, self.gridW do self.slots[x] = {} for y = 1, self.gridH do local slot = self:Add("DPanel") slot:SetZPos(-999) slot.gridX = x slot.gridY = y slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2)) slot:SetSize(iconSize, iconSize) slot.Paint = function(panel, width, height) derma.SkinFunc("PaintInventorySlot", panel, width, height) end self.slots[x][y] = slot end end end function PANEL:RebuildItems() local iconSize = self.iconSize for x = 1, self.gridW do for y = 1, self.gridH do local slot = self.slots[x][y] slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2)) slot:SetSize(iconSize, iconSize) end end for _, v in pairs(self.panels) do if (IsValid(v)) then v:SetPos(self.slots[v.gridX][v.gridY]:GetPos()) v:SetSize(v.gridW * iconSize, v.gridH * iconSize) end end end function PANEL:PaintDragPreview(width, height, mouseX, mouseY, itemPanel) local iconSize = self.iconSize local item = itemPanel:GetItemTable() if (item) then local inventory = ix.item.inventories[self.invID] local dropX = math.ceil((mouseX - 4 - (itemPanel.gridW - 1) * 32) / iconSize) local dropY = math.ceil((mouseY - self:GetPadding(2) - (itemPanel.gridH - 1) * 32) / iconSize) local hoveredPanel = vgui.GetHoveredPanel() if (IsValid(hoveredPanel) and hoveredPanel != itemPanel and hoveredPanel.GetItemTable) then local hoveredItem = hoveredPanel:GetItemTable() if (hoveredItem) then local info = hoveredItem.functions.combine if (info and info.OnCanRun and info.OnCanRun(hoveredItem, {item.id}) != false) then surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", self, Color(200, 0, 0)), 20)) surface.DrawRect( hoveredPanel.x, hoveredPanel.y, hoveredPanel:GetWide(), hoveredPanel:GetTall() ) self.combineItem = hoveredItem return end end end self.combineItem = nil -- don't draw grid if we're dragging it out of bounds if (inventory) then local invWidth, invHeight = inventory:GetSize() if (dropX < 1 or dropY < 1 or dropX + itemPanel.gridW - 1 > invWidth or dropY + itemPanel.gridH - 1 > invHeight) then return end end local bEmpty = true for x = 0, itemPanel.gridW - 1 do for y = 0, itemPanel.gridH - 1 do local x2 = dropX + x local y2 = dropY + y bEmpty = self:IsEmpty(x2, y2, itemPanel) if (!bEmpty) then -- no need to iterate further since we know something is blocking the hovered grid cells, break through both loops goto finish end end end ::finish:: local previewColor = ColorAlpha(derma.GetColor(bEmpty and "Success" or "Error", self, Color(200, 0, 0)), 20) surface.SetDrawColor(previewColor) surface.DrawRect( (dropX - 1) * iconSize + 4, (dropY - 1) * iconSize + self:GetPadding(2), itemPanel:GetWide(), itemPanel:GetTall() ) end end function PANEL:PaintOver(width, height) local panel = self.previewPanel if (IsValid(panel)) then local itemPanel = (dragndrop.GetDroppable() or {})[1] if (IsValid(itemPanel)) then self:PaintDragPreview(width, height, self.previewX, self.previewY, itemPanel) end end self.previewPanel = nil end function PANEL:IsEmpty(x, y, this) return (self.slots[x] and self.slots[x][y]) and (!IsValid(self.slots[x][y].item) or self.slots[x][y].item == this) end function PANEL:IsAllEmpty(x, y, width, height, this) for x2 = 0, width - 1 do for y2 = 0, height - 1 do if (!self:IsEmpty(x + x2, y + y2, this)) then return false end end end return true end function PANEL:OnTransfer(oldX, oldY, x, y, oldInventory, noSend) local inventories = ix.item.inventories local inventory = inventories[oldInventory.invID] local inventory2 = inventories[self.invID] local item if (inventory) then item = inventory:GetItemAt(oldX, oldY) if (!item) then return false end if (hook.Run("CanTransferItem", item, inventories[oldInventory.invID], inventories[self.invID]) == false) then return false, "notAllowed" end if (item.CanTransfer and item:CanTransfer(inventory, inventory != inventory2 and inventory2 or nil) == false) then return false end end if (!noSend) then net.Start("ixInventoryMove") net.WriteUInt(oldX, 6) net.WriteUInt(oldY, 6) net.WriteUInt(x, 6) net.WriteUInt(y, 6) net.WriteUInt(oldInventory.invID, 32) net.WriteUInt(self != oldInventory and self.invID or oldInventory.invID, 32) net.SendToServer() end if (inventory) then inventory.slots[oldX][oldY] = nil end if (item and inventory2) then inventory2.slots[x] = inventory2.slots[x] or {} inventory2.slots[x][y] = item end end function PANEL:AddIcon(model, x, y, w, h, skin) local iconSize = self.iconSize w = w or 1 h = h or 1 if (self.slots[x] and self.slots[x][y]) then local panel = self:Add("ixItemIcon") panel:SetSize(w * iconSize, h * iconSize) panel:SetZPos(999) panel:InvalidateLayout(true) panel:SetModel(model, skin) panel:SetPos(self.slots[x][y]:GetPos()) panel.gridX = x panel.gridY = y panel.gridW = w panel.gridH = h local inventory = ix.item.inventories[self.invID] if (!inventory) then return end local itemTable = inventory:GetItemAt(panel.gridX, panel.gridY) panel:SetInventoryID(inventory:GetID()) panel:SetItemTable(itemTable) if (self.panels[itemTable:GetID()]) then self.panels[itemTable:GetID()]:Remove() end if (itemTable.exRender) then panel.Icon:SetVisible(false) panel.ExtraPaint = function(this, panelX, panelY) local exIcon = ikon:GetIcon(itemTable.uniqueID) if (exIcon) then surface.SetMaterial(exIcon) surface.SetDrawColor(color_white) surface.DrawTexturedRect(0, 0, panelX, panelY) else ikon:renderIcon( itemTable.uniqueID, itemTable.width, itemTable.height, itemTable:GetModel(), itemTable.iconCam ) end end else -- yeah.. RenderNewIcon(panel, itemTable) end panel.slots = {} for i = 0, w - 1 do for i2 = 0, h - 1 do local slot = self.slots[x + i] and self.slots[x + i][y + i2] if (IsValid(slot)) then slot.item = panel panel.slots[#panel.slots + 1] = slot else for _, v in ipairs(panel.slots) do v.item = nil end panel:Remove() return end end end return panel end end function PANEL:ReceiveDrop(panels, bDropped, menuIndex, x, y) local panel = panels[1] if (!IsValid(panel)) then self.previewPanel = nil return end if (bDropped) then local inventory = ix.item.inventories[self.invID] if (inventory and panel.OnDrop) then local dropX = math.ceil((x - 4 - (panel.gridW - 1) * 32) / self.iconSize) local dropY = math.ceil((y - self:GetPadding(2) - (panel.gridH - 1) * 32) / self.iconSize) panel:OnDrop(true, self, inventory, dropX, dropY) end self.previewPanel = nil else self.previewPanel = panel self.previewX = x self.previewY = y end end vgui.Register("ixInventory", PANEL, "DFrame") hook.Add("CreateMenuButtons", "ixInventory", function(tabs) if (hook.Run("CanPlayerViewInventory") == false) then return end tabs["inv"] = { bDefault = true, Create = function(info, container) local canvas = container:Add("DTileLayout") local canvasLayout = canvas.PerformLayout canvas.PerformLayout = nil -- we'll layout after we add the panels instead of each time one is added canvas:SetBorder(0) canvas:SetSpaceX(2) canvas:SetSpaceY(2) canvas:Dock(FILL) ix.gui.menuInventoryContainer = canvas local panel = canvas:Add("ixInventory") panel:SetPos(0, 0) panel:SetDraggable(false) panel:SetSizable(false) panel:SetTitle(nil) panel.bNoBackgroundBlur = true panel.childPanels = {} local inventory = LocalPlayer():GetCharacter():GetInventory() if (inventory) then panel:SetInventory(inventory) end ix.gui.inv1 = panel if (ix.option.Get("openBags", true)) then for k, _ in inventory:Iter() do if (!k.isBag) then continue end k.functions.View.OnClick(k) end end canvas.PerformLayout = canvasLayout canvas:Layout() end } end) hook.Add("PostRenderVGUI", "ixInvHelper", function() local pnl = ix.gui.inv1 hook.Run("PostDrawInventory", pnl) end) ================================================ FILE: gamemode/core/derma/cl_menu.lua ================================================ local animationTime = 1 local matrixZScale = Vector(1, 1, 0.0001) DEFINE_BASECLASS("ixSubpanelParent") local PANEL = {} AccessorFunc(PANEL, "bCharacterOverview", "CharacterOverview", FORCE_BOOL) function PANEL:Init() if (IsValid(ix.gui.menu)) then ix.gui.menu:Remove() end ix.gui.menu = self -- properties self.manualChildren = {} self.noAnchor = CurTime() + 0.4 self.anchorMode = true self.rotationOffset = Angle(0, 180, 0) self.projectedTexturePosition = Vector(0, 0, 6) self.projectedTextureRotation = Angle(-45, 60, 0) self.bCharacterOverview = false self.bOverviewOut = false self.overviewFraction = 0 self.currentAlpha = 0 self.currentBlur = 0 -- setup self:SetPadding(ScreenScale(16), true) self:SetSize(ScrW(), ScrH()) self:SetPos(0, 0) self:SetLeftOffset(self:GetWide() * 0.25 + self:GetPadding()) -- main button panel self.buttons = self:Add("Panel") self.buttons:SetSize(self:GetWide() * 0.25, self:GetTall() - self:GetPadding() * 2) self.buttons:Dock(LEFT) self.buttons:SetPaintedManually(true) local close = self.buttons:Add("ixMenuButton") close:SetText("return") close:SizeToContents() close:Dock(BOTTOM) close.DoClick = function() self:Remove() end local characters = self.buttons:Add("ixMenuButton") characters:SetText("characters") characters:SizeToContents() characters:Dock(BOTTOM) characters.DoClick = function() self:Remove() vgui.Create("ixCharMenu") end -- @todo make a better way to avoid clicks in the padding PLEASE self.guard = self:Add("Panel") self.guard:SetPos(0, 0) self.guard:SetSize(self:GetPadding(), self:GetTall()) -- tabs self.tabs = self.buttons:Add("DScrollPanel") self.tabs.buttons = {} self.tabs:Dock(FILL) self:PopulateTabs() self:MakePopup() self:OnOpened() end function PANEL:OnOpened() self:SetAlpha(0) self:CreateAnimation(animationTime, { target = {currentAlpha = 255}, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.currentAlpha) end }) end function PANEL:GetActiveTab() return (self:GetActiveSubpanel() or {}).subpanelName end function PANEL:TransitionSubpanel(id) local lastSubpanel = self:GetActiveSubpanel() -- don't transition to the same panel if (IsValid(lastSubpanel) and lastSubpanel.subpanelID == id) then return end local subpanel = self:GetSubpanel(id) if (IsValid(subpanel)) then if (!subpanel.bPopulated) then -- we need to set the size of the subpanel if it's a section since it will be 0, 0 if (subpanel.sectionParent) then subpanel:SetSize(self:GetStandardSubpanelSize()) end local info = subpanel.info subpanel.Paint = nil if (istable(info) and info.Create) then info:Create(subpanel) elseif (isfunction(info)) then info(subpanel) end hook.Run("MenuSubpanelCreated", subpanel.subpanelName, subpanel) subpanel.bPopulated = true end -- only play whoosh sound only when the menu was already open if (IsValid(lastSubpanel)) then LocalPlayer():EmitSound("Helix.Whoosh") end self:SetActiveSubpanel(id) end subpanel = self:GetActiveSubpanel() local info = subpanel.info local bHideBackground = istable(info) and (info.bHideBackground != nil and info.bHideBackground or false) or false if (bHideBackground) then self:HideBackground() else self:ShowBackground() end -- call hooks if we've changed subpanel if (IsValid(lastSubpanel) and istable(lastSubpanel.info) and lastSubpanel.info.OnDeselected) then lastSubpanel.info:OnDeselected(lastSubpanel) end if (IsValid(subpanel) and istable(subpanel.info) and subpanel.info.OnSelected) then subpanel.info:OnSelected(subpanel) end ix.gui.lastMenuTab = id end function PANEL:SetCharacterOverview(bValue, length) bValue = tobool(bValue) length = length or animationTime if (bValue) then if (!IsValid(self.projectedTexture)) then self.projectedTexture = ProjectedTexture() end local faction = ix.faction.indices[LocalPlayer():Team()] local color = faction and faction.color or color_white self.projectedTexture:SetEnableShadows(false) self.projectedTexture:SetNearZ(12) self.projectedTexture:SetFarZ(64) self.projectedTexture:SetFOV(90) self.projectedTexture:SetColor(color) self.projectedTexture:SetTexture("effects/flashlight/soft") self:CreateAnimation(length, { index = 3, target = {overviewFraction = 1}, easing = "outQuint", bIgnoreConfig = true }) self.bOverviewOut = false self.bCharacterOverview = true else self:CreateAnimation(length, { index = 3, target = {overviewFraction = 0}, easing = "outQuint", bIgnoreConfig = true, OnComplete = function(animation, panel) panel.bCharacterOverview = false if (IsValid(panel.projectedTexture)) then panel.projectedTexture:Remove() end end }) self.bOverviewOut = true end end function PANEL:GetOverviewInfo(origin, angles, fov) local originAngles = Angle(0, angles.yaw, angles.roll) local target = LocalPlayer():GetObserverTarget() local fraction = self.overviewFraction local bDrawPlayer = ((fraction > 0.2) or (!self.bOverviewOut and (fraction > 0.2))) and !IsValid(target) local forward = originAngles:Forward() * 58 - originAngles:Right() * 16 forward.z = 0 local newOrigin if (IsValid(target)) then newOrigin = target:GetPos() + forward else newOrigin = origin - LocalPlayer():OBBCenter() * 0.6 + forward end local newAngles = originAngles + self.rotationOffset newAngles.pitch = 5 newAngles.roll = 0 return LerpVector(fraction, origin, newOrigin), LerpAngle(fraction, angles, newAngles), Lerp(fraction, fov, 90), bDrawPlayer end function PANEL:HideBackground() self:CreateAnimation(animationTime, { index = 2, target = {currentBlur = 0}, easing = "outQuint" }) end function PANEL:ShowBackground() self:CreateAnimation(animationTime, { index = 2, target = {currentBlur = 1}, easing = "outQuint" }) end function PANEL:GetStandardSubpanelSize() return ScrW() * 0.75 - self:GetPadding() * 3, ScrH() - self:GetPadding() * 2 end function PANEL:SetupTab(name, info, sectionParent) local bTable = istable(info) local buttonColor = (bTable and info.buttonColor) or (ix.config.Get("color") or Color(140, 140, 140, 255)) local bDefault = (bTable and info.bDefault) or false local qualifiedName = sectionParent and (sectionParent.name .. "/" .. name) or name -- setup subpanels without populating them so we can retain the order local subpanel = self:AddSubpanel(qualifiedName, true) local id = subpanel.subpanelID subpanel.info = info subpanel.sectionParent = sectionParent and qualifiedName subpanel:SetPaintedManually(true) subpanel:SetTitle(nil) if (sectionParent) then -- hide section subpanels if they haven't been populated to seeing more subpanels than necessary -- fly by as you navigate tabs in the menu subpanel:SetSize(0, 0) else subpanel:SetSize(self:GetStandardSubpanelSize()) -- this is called while the subpanel has not been populated subpanel.Paint = function(panel, width, height) derma.SkinFunc("PaintPlaceholderPanel", panel, width, height) end end local button if (sectionParent) then button = sectionParent:AddSection(L(name)) name = qualifiedName else button = self.tabs:Add("ixMenuSelectionButton") button:SetText(L(name)) button:SizeToContents() button:Dock(TOP) button:SetButtonList(self.tabs.buttons) button:SetBackgroundColor(buttonColor) end button.name = name button.id = id button.OnSelected = function() self:TransitionSubpanel(id) end if (bTable and info.PopulateTabButton) then info:PopulateTabButton(button) end -- don't allow sections in sections if (sectionParent or !bTable or !info.Sections) then return bDefault, button, subpanel end -- create button sections for sectionName, sectionInfo in pairs(info.Sections) do self:SetupTab(sectionName, sectionInfo, button) end return bDefault, button, subpanel end function PANEL:PopulateTabs() local default local tabs = {} hook.Run("CreateMenuButtons", tabs) for name, info in SortedPairs(tabs) do local bDefault, button = self:SetupTab(name, info) if (bDefault) then default = button end end if (ix.gui.lastMenuTab) then for i = 1, #self.tabs.buttons do local button = self.tabs.buttons[i] if (button.id == ix.gui.lastMenuTab) then default = button break end end end if (!IsValid(default) and #self.tabs.buttons > 0) then default = self.tabs.buttons[1] end if (IsValid(default)) then default:SetSelected(true) self:SetActiveSubpanel(default.id, 0) end self.buttons:MoveToFront() self.guard:MoveToBefore(self.buttons) end function PANEL:AddManuallyPaintedChild(panel) panel:SetParent(self) panel:SetPaintedManually(panel) self.manualChildren[#self.manualChildren + 1] = panel end function PANEL:OnKeyCodePressed(key) self.noAnchor = CurTime() + 0.5 if (key == KEY_TAB) then self:Remove() end end function PANEL:Think() if (IsValid(self.projectedTexture)) then local forward = LocalPlayer():GetForward() forward.z = 0 local right = LocalPlayer():GetRight() right.z = 0 self.projectedTexture:SetBrightness(self.overviewFraction * 4) self.projectedTexture:SetPos(LocalPlayer():GetPos() + right * 16 - forward * 8 + self.projectedTexturePosition) self.projectedTexture:SetAngles(forward:Angle() + self.projectedTextureRotation) self.projectedTexture:Update() end if (self.bClosing) then return end local bTabDown = input.IsKeyDown(KEY_TAB) if (bTabDown and (self.noAnchor or CurTime() + 0.4) < CurTime() and self.anchorMode) then self.anchorMode = false surface.PlaySound("buttons/lightswitch2.wav") end if ((!self.anchorMode and !bTabDown) or gui.IsGameUIVisible()) then self:Remove() if (ix.option.Get("escCloseMenu", false)) then gui.HideGameUI() end end end function PANEL:Paint(width, height) derma.SkinFunc("PaintMenuBackground", self, width, height, self.currentBlur) local bShouldScale = self.currentAlpha != 255 if (bShouldScale) then local currentScale = Lerp(self.currentAlpha / 255, 0.9, 1) local matrix = Matrix() matrix:Scale(matrixZScale * currentScale) matrix:Translate(Vector( ScrW() * 0.5 - (ScrW() * currentScale * 0.5), ScrH() * 0.5 - (ScrH() * currentScale * 0.5), 1 )) cam.PushModelMatrix(matrix) end BaseClass.Paint(self, width, height) self:PaintSubpanels(width, height) self.buttons:PaintManual() for i = 1, #self.manualChildren do self.manualChildren[i]:PaintManual() end if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then for i = 1, #ix.gui.inv1.childPanels do local panel = ix.gui.inv1.childPanels[i] if (IsValid(panel)) then panel:PaintManual() end end end if (bShouldScale) then cam.PopModelMatrix() end end function PANEL:PerformLayout() self.guard:SetSize(self.tabs:GetWide() + self:GetPadding() * 2, self:GetTall()) end function PANEL:Remove() self.bClosing = true self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) self:SetCharacterOverview(false, animationTime * 0.5) -- remove input from opened child panels since they grab focus if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then for i = 1, #ix.gui.inv1.childPanels do local panel = ix.gui.inv1.childPanels[i] if (IsValid(panel)) then panel:SetMouseInputEnabled(false) panel:SetKeyboardInputEnabled(false) end end end CloseDermaMenus() gui.EnableScreenClicker(false) self:CreateAnimation(animationTime * 0.5, { index = 2, target = {currentBlur = 0}, easing = "outQuint" }) self:CreateAnimation(animationTime * 0.5, { target = {currentAlpha = 0}, easing = "outQuint", -- we don't animate the blur because blurring doesn't draw things -- with amount < 1 very well, resulting in jarring transition Think = function(animation, panel) panel:SetAlpha(panel.currentAlpha) end, OnComplete = function(animation, panel) if (IsValid(panel.projectedTexture)) then panel.projectedTexture:Remove() end BaseClass.Remove(panel) end }) end vgui.Register("ixMenu", PANEL, "ixSubpanelParent") if (IsValid(ix.gui.menu)) then ix.gui.menu:Remove() end ix.gui.lastMenuTab = nil ================================================ FILE: gamemode/core/derma/cl_menubutton.lua ================================================ local buttonPadding = ScreenScale(14) * 0.5 local animationTime = 0.5 -- base menu button DEFINE_BASECLASS("DButton") local PANEL = {} AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha") function PANEL:Init() self:SetFont("ixMenuButtonFont") self:SetTextColor(color_white) self:SetPaintBackground(false) self:SetContentAlignment(4) self:SetTextInset(buttonPadding, 0) self.padding = {32, 12, 32, 12} -- left, top, right, bottom self.backgroundColor = Color(0, 0, 0) self.backgroundAlpha = 128 self.currentBackgroundAlpha = 0 end function PANEL:GetPadding() return self.padding end function PANEL:SetPadding(left, top, right, bottom) self.padding = { left or self.padding[1], top or self.padding[2], right or self.padding[3], bottom or self.padding[4] } end function PANEL:SetText(text, noTranslation) BaseClass.SetText(self, noTranslation and text:utf8upper() or L(text):utf8upper()) end function PANEL:SizeToContents() BaseClass.SizeToContents(self) local width, height = self:GetSize() self:SetSize(width + self.padding[1] + self.padding[3], height + self.padding[2] + self.padding[4]) end function PANEL:PaintBackground(width, height) surface.SetDrawColor(ColorAlpha(self.backgroundColor, self.currentBackgroundAlpha)) surface.DrawRect(0, 0, width, height) end function PANEL:Paint(width, height) self:PaintBackground(width, height) BaseClass.Paint(self, width, height) end function PANEL:SetTextColorInternal(color) BaseClass.SetTextColor(self, color) self:SetFGColor(color) end function PANEL:SetTextColor(color) self:SetTextColorInternal(color) self.color = color end function PANEL:SetDisabled(bValue) local color = self.color if (bValue) then self:SetTextColorInternal(Color(math.max(color.r - 60, 0), math.max(color.g - 60, 0), math.max(color.b - 60, 0))) else self:SetTextColorInternal(color) end BaseClass.SetDisabled(self, bValue) end function PANEL:OnCursorEntered() if (self:GetDisabled()) then return end local color = self:GetTextColor() self:SetTextColorInternal(Color(math.max(color.r - 25, 0), math.max(color.g - 25, 0), math.max(color.b - 25, 0))) self:CreateAnimation(0.15, { target = {currentBackgroundAlpha = self.backgroundAlpha} }) LocalPlayer():EmitSound("Helix.Rollover") end function PANEL:OnCursorExited() if (self:GetDisabled()) then return end if (self.color) then self:SetTextColor(self.color) else self:SetTextColor(color_white) end self:CreateAnimation(0.15, { target = {currentBackgroundAlpha = 0} }) end function PANEL:OnMousePressed(code) if (self:GetDisabled()) then return end if (self.color) then self:SetTextColor(self.color) else self:SetTextColor(ix.config.Get("color")) end LocalPlayer():EmitSound("Helix.Press") if (code == MOUSE_LEFT and self.DoClick) then self:DoClick(self) elseif (code == MOUSE_RIGHT and self.DoRightClick) then self:DoRightClick(self) end end function PANEL:OnMouseReleased(key) if (self:GetDisabled()) then return end if (self.color) then self:SetTextColor(self.color) else self:SetTextColor(color_white) end end vgui.Register("ixMenuButton", PANEL, "DButton") -- selection menu button DEFINE_BASECLASS("ixMenuButton") PANEL = {} AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") AccessorFunc(PANEL, "selected", "Selected", FORCE_BOOL) AccessorFunc(PANEL, "buttonList", "ButtonList") function PANEL:Init() self.backgroundColor = color_white self.selected = false self.buttonList = {} self.sectionPanel = nil -- sub-sections this button has; created only if it has any sections end function PANEL:PaintBackground(width, height) local alpha = self.selected and 255 or self.currentBackgroundAlpha derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ColorAlpha(self.backgroundColor, alpha)) end function PANEL:SetSelected(bValue, bSelectedSection) self.selected = bValue if (bValue) then self:OnSelected() if (self.sectionPanel) then self.sectionPanel:Show() elseif (self.sectionParent) then self.sectionParent.sectionPanel:Show() end elseif (self.sectionPanel and self.sectionPanel:IsVisible() and !bSelectedSection) then self.sectionPanel:Hide() end end function PANEL:SetButtonList(list, bNoAdd) if (!bNoAdd) then list[#list + 1] = self end self.buttonList = list end function PANEL:GetSectionPanel() return self.sectionPanel end function PANEL:AddSection(name) if (!IsValid(self.sectionPanel)) then -- add section panel to regular button list self.sectionPanel = vgui.Create("ixMenuSelectionList", self:GetParent()) self.sectionPanel:Dock(self:GetDock()) self.sectionPanel:SetParentButton(self) end return self.sectionPanel:AddButton(name, self.buttonList) end function PANEL:OnMousePressed(key) for _, v in pairs(self.buttonList) do if (IsValid(v) and v != self) then v:SetSelected(false, self.sectionParent == v) end end self:SetSelected(true) BaseClass.OnMousePressed(self, key) end function PANEL:OnSelected() end vgui.Register("ixMenuSelectionButton", PANEL, "ixMenuButton") -- collapsable list for menu button sections PANEL = {} AccessorFunc(PANEL, "parent", "ParentButton") function PANEL:Init() self.parent = nil -- button that is responsible for controlling this list self.height = 0 self.targetHeight = 0 self:DockPadding(0, 1, 0, 1) self:SetVisible(false) self:SetTall(0) end function PANEL:AddButton(name, buttonList) assert(IsValid(self.parent), "attempted to add button to ixMenuSelectionList without a ParentButton") assert(buttonList ~= nil, "attempted to add button to ixMenuSelectionList without a buttonList") local button = self:Add("ixMenuSelectionButton") button.sectionParent = self.parent button:SetTextInset(buttonPadding * 2, 0) button:SetPadding(nil, 8, nil, 8) button:SetFont("ixMenuButtonFontSmall") button:Dock(TOP) button:SetText(name) button:SizeToContents() button:SetButtonList(buttonList) button:SetBackgroundColor(self.parent:GetBackgroundColor()) self.targetHeight = self.targetHeight + button:GetTall() return button end function PANEL:Show() self:SetVisible(true) self:CreateAnimation(animationTime, { index = 1, target = { height = self.targetHeight + 2 -- +2 for padding }, easing = "outQuart", Think = function(animation, panel) panel:SetTall(panel.height) end }) end function PANEL:Hide() self:CreateAnimation(animationTime, { index = 1, target = { height = 0 }, easing = "outQuint", Think = function(animation, panel) panel:SetTall(panel.height) end, OnComplete = function(animation, panel) panel:SetVisible(false) end }) end function PANEL:Paint(width, height) surface.SetDrawColor(Color(255, 255, 255, 33)) surface.DrawRect(0, 0, width, 1) surface.DrawRect(0, height - 1, width, 1) end vgui.Register("ixMenuSelectionList", PANEL, "Panel") ================================================ FILE: gamemode/core/derma/cl_modelpanel.lua ================================================ DEFINE_BASECLASS("DModelPanel") local PANEL = {} local MODEL_ANGLE = Angle(0, 45, 0) function PANEL:Init() self.brightness = 1 self:SetCursor("none") end function PANEL:SetModel(model, skin, bodygroups) if (IsValid(self.Entity)) then self.Entity:Remove() self.Entity = nil end if (!ClientsideModel) then return end local entity = ClientsideModel(model, RENDERGROUP_OPAQUE) if (!IsValid(entity)) then return end entity:SetNoDraw(true) entity:SetIK(false) if (skin) then entity:SetSkin(skin) end if (isstring(bodygroups)) then entity:SetBodyGroups(bodygroups) end local sequence = entity:LookupSequence("idle_unarmed") if (sequence <= 0) then sequence = entity:SelectWeightedSequence(ACT_IDLE) end if (sequence > 0) then entity:ResetSequence(sequence) else local found = false for _, v in ipairs(entity:GetSequenceList()) do if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then entity:ResetSequence(v) found = true break end end if (!found) then entity:ResetSequence(4) end end self.Entity = entity end function PANEL:LayoutEntity() local scrW, scrH = ScrW(), ScrH() local xRatio = gui.MouseX() / scrW local yRatio = gui.MouseY() / scrH local x, _ = self:LocalToScreen(self:GetWide() / 2) local xRatio2 = x / scrW local entity = self.Entity entity:SetPoseParameter("head_pitch", yRatio*90 - 30) entity:SetPoseParameter("head_yaw", (xRatio - xRatio2)*90 - 5) entity:SetAngles(MODEL_ANGLE) entity:SetIK(false) if (self.copyLocalSequence) then entity:SetSequence(LocalPlayer():GetSequence()) entity:SetPoseParameter("move_yaw", 360 * LocalPlayer():GetPoseParameter("move_yaw") - 180) end self:RunAnimation() end function PANEL:DrawModel() local brightness = self.brightness * 0.4 local brightness2 = self.brightness * 1.5 render.SetStencilEnable(false) render.SetColorMaterial() render.SetColorModulation(1, 1, 1) render.SetModelLighting(0, brightness2, brightness2, brightness2) for i = 1, 4 do render.SetModelLighting(i, brightness, brightness, brightness) end local fraction = (brightness / 1) * 0.1 render.SetModelLighting(5, fraction, fraction, fraction) -- Excecute Some stuffs if (self.enableHook) then hook.Run("DrawHelixModelView", self, self.Entity) end self.Entity:DrawModel() if (self.enableHook) then hook.Run("PostDrawHelixModelView", self, self.Entity) end end function PANEL:OnMousePressed() end vgui.Register("ixModelPanel", PANEL, "DModelPanel") ================================================ FILE: gamemode/core/derma/cl_notice.lua ================================================ local animationTime = 0.75 -- notice manager -- this manages positions/animations for notice panels local PANEL = {} AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) function PANEL:Init() self:SetSize(ScrW() * 0.4, ScrH()) self:SetPos(ScrW() - ScrW() * 0.4, 0) self:SetZPos(-99999) self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) self.notices = {} self.padding = 4 end function PANEL:GetAll() return self.notices end function PANEL:Clear() for _, v in ipairs(self.notices) do self:RemoveNotice(v) end end function PANEL:AddNotice(text, bError) if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) then return end local textLength = text:utf8len() local panel = self:Add("ixNotice") panel:SetText(text) panel:SetError(bError or text:utf8sub(textLength, textLength) == "!") panel:SizeToContents() panel.currentY = -panel:GetTall() panel:SetPos(self.padding, panel.currentY) -- setup duration timer panel:CreateAnimation(ix.option.Get("noticeDuration", 8), { index = 2, target = {duration = 1}, bIgnoreConfig = true, OnComplete = function(animation, this) self:RemoveNotice(this) end }) table.insert(self.notices, 1, panel) self:Organize() -- remove old notice if we've hit the limit of notices if (#self.notices > ix.option.Get("noticeMax", 4)) then for i = #self.notices, 1, -1 do local notice = self.notices[i] if (IsValid(notice) and !notice.bClosing) then self:RemoveNotice(notice) break end end end return panel end function PANEL:RemoveNotice(panel) panel.bClosing = true panel:CreateAnimation(animationTime, { index = 3, target = {outAnimation = 0}, easing = "outQuint", OnComplete = function(animation, this) local toRemove for k, v in ipairs(self.notices) do if (v == this) then toRemove = k break end end if (toRemove) then table.remove(self.notices, toRemove) end this:SetText("") -- (hack) text remains for a frame after remove is called, so let's make sure we don't draw it this:Remove() end }) end -- update target Y positions and animations function PANEL:Organize() local currentTarget = self.padding for _, v in ipairs(self.notices) do v:CreateAnimation(animationTime, { index = 1, target = {currentY = currentTarget}, easing = "outElastic", Think = function(animation, panel) panel:SetPos( self:GetWide() - panel:GetWide() - self.padding, math.min(panel.currentY + 1, currentTarget) -- easing eventually hits subpixel movement so we level it off ) end }) currentTarget = currentTarget + self.padding + v:GetTall() end end vgui.Register("ixNoticeManager", PANEL, "Panel") -- notice panel -- these do not manage their own enter/exit animations or lifetime DEFINE_BASECLASS("DLabel") PANEL = {} AccessorFunc(PANEL, "bError", "Error", FORCE_BOOL) AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) function PANEL:Init() self:SetSize(256, 36) self:SetContentAlignment(5) self:SetExpensiveShadow(1, Color(0, 0, 0, 150)) self:SetFont("ixNoticeFont") self:SetTextColor(color_white) self:SetDrawOnTop(true) self:DockPadding(0, 0, 0, 0) self:DockMargin(0, 0, 0, 0) self.bError = false self.bHovered = false self.errorAnimation = 0 self.padding = 8 self.currentY = 0 self.duration = 0 self.outAnimation = 1 self.alpha = 255 LocalPlayer():EmitSound("Helix.Notify") end function PANEL:SetError(bValue) self.bError = tobool(bValue) if (bValue) then self.errorAnimation = 1 self:CreateAnimation(animationTime, { index = 5, target = {errorAnimation = 0}, easing = "outQuint" }) end end function PANEL:SizeToContents() local contentWidth, contentHeight = self:GetContentSize() contentWidth = contentWidth + self.padding * 2 contentHeight = contentHeight + self.padding * 2 local manager = ix.gui.notices local maxWidth = math.min(IsValid(manager) and (manager:GetWide() - manager:GetPadding() * 2) or ScrW(), contentWidth) if (contentWidth > maxWidth) then self:SetWide(maxWidth) self:SetTextInset(self.padding * 2, 0) self:SetWrap(true) self:SizeToContentsY() self:SetWide(self:GetContentSize()) else self:SetSize(contentWidth, contentHeight) end end function PANEL:SizeToContentsY() BaseClass.SizeToContentsY(self) self:SetTall(self:GetTall() + self.padding * 2) end function PANEL:OnMouseHover() self:CreateAnimation(animationTime * 0.5, { index = 4, target = {alpha = 0}, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.alpha) end }) end function PANEL:OnMouseLeave() self:CreateAnimation(animationTime * 0.5, { index = 4, target = {alpha = 255}, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.alpha) end }) end function PANEL:Paint(width, height) if (self.outAnimation < 1) then local x, y = self:LocalToScreen(0, 0) render.SetScissorRect(x, y, x + self:GetWide(), y + (self:GetTall() * self.outAnimation), true) end local x, y = self:LocalToScreen(0, 0) local mouseX, mouseY = gui.MousePos() if (mouseX >= x and mouseX <= x + width and mouseY >= y and mouseY <= y + height) then if (!self.bHovered) then self.bHovered = true self:OnMouseHover() end elseif (self.bHovered) then self.bHovered = false self:OnMouseLeave() end ix.util.DrawBlur(self) if (self.errorAnimation > 0) then local color = derma.GetColor("Error", self) surface.SetDrawColor( color.r * self.errorAnimation, color.g * self.errorAnimation, color.b * self.errorAnimation, self.errorAnimation * 255 + ((1 - self.errorAnimation) * 66) ) else surface.SetDrawColor(0, 0, 0, 66) end surface.DrawRect(0, 0, width, height) surface.SetDrawColor(self.bError and derma.GetColor("Error", self) or ix.config.Get("color")) surface.DrawRect(0, height - 1, width * self.duration, 1) end function PANEL:PaintOver(width, height) render.SetScissorRect(0, 0, 0, 0, false) end vgui.Register("ixNotice", PANEL, "DLabel") if (IsValid(ix.gui.notices)) then ix.gui.notices:Remove() ix.gui.notices = vgui.Create("ixNoticeManager") else ix.gui.notices = vgui.Create("ixNoticeManager") end ================================================ FILE: gamemode/core/derma/cl_noticebar.lua ================================================ local PANEL = { types = { "Info", -- info "Success", -- success "Error" -- error } } AccessorFunc(PANEL, "type", "Type", FORCE_NUMBER) AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) AccessorFunc(PANEL, "length", "Length", FORCE_NUMBER) AccessorFunc(PANEL, "hidden", "Hidden", FORCE_BOOL) function PANEL:Init() self.type = 1 self.padding = 8 self.length = 4 self.currentY = 0 self.hidden = true self.text = self:Add("DLabel") self.text:SetFont("ixNoticeFont") self.text:SetContentAlignment(5) self.text:SetTextColor(color_white) self.text:SizeToContents() self.text:Dock(FILL) self:SetSize(self:GetParent():GetWide() - (self.padding * 4), self.text:GetTall() + (self.padding * 2)) self:SetPos(self.padding * 2, -self:GetTall() - self.padding) end function PANEL:SetFont(value) self.text:SetFont(value) self.text:SizeToContents() end function PANEL:SetText(text) self.text:SetText(text) self.text:SizeToContents() end function PANEL:Slide(direction, length) direction = direction or "up" length = length or 0.5 timer.Remove("ixNoticeBarAnimation") local x, _ = self:GetPos() local baseY = direction == "up" and self.padding * 2 or (-self:GetTall() - self.padding) local targetY = direction == "up" and (-self:GetTall() - self.padding) or self.padding * 2 local easing = direction == "up" and "outQuint" or "outElastic" self:SetPos(x, baseY) self.currentY = baseY self.hidden = direction == "up" self:CreateAnimation(length, { target = {currentY = targetY}, easing = easing, Think = function(animation, panel) local lastX, _ = panel:GetPos() panel:SetPos(lastX, panel.currentY) end }) end function PANEL:Show(bRemove) self:Slide("down") timer.Create("ixNoticeBarAnimation", self.length - 0.5, 1, function() if (!IsValid(self)) then return end self:Slide("up") end) end function PANEL:Paint(width, height) local color = derma.GetColor(self.types[self.type], self) surface.SetDrawColor(color) surface.DrawRect(0, 0, width, height) end vgui.Register("ixNoticeBar", PANEL, "Panel") ================================================ FILE: gamemode/core/derma/cl_overrides.lua ================================================ -- overrides standard derma panels to add/change functionality local PANEL = {} local OVERRIDES = {} -- @todo remove me when autorefresh support is no longer needed local function OverridePanel(name, func) PANEL = vgui.GetControlTable(name) if (!istable(PANEL)) then return end OVERRIDES = {} func() for k, _ in pairs(PANEL) do local overrideName = "ix" .. k if (PANEL[overrideName] and !OVERRIDES[k]) then print("unhooking override ", overrideName) PANEL[k] = PANEL[overrideName] PANEL[overrideName] = nil end end end local function Override(name) local oldMethod = "ix" .. name OVERRIDES[name] = true if (PANEL[oldMethod]) then return end PANEL[oldMethod] = PANEL[name] end OverridePanel("DMenuOption", function() function PANEL:PerformLayout() self:SizeToContents() self:SetWide(self:GetWide() + 30) local w = math.max(self:GetParent():GetWide(), self:GetWide()) self:SetSize(w, self:GetTall() + 4) if (self.SubMenuArrow) then self.SubMenuArrow:SetSize(15, 15) self.SubMenuArrow:CenterVertical() self.SubMenuArrow:AlignRight(4) end DButton.PerformLayout(self) end end) OverridePanel("DMenu", function() local animationTime = 0.33 Override("Init") function PANEL:Init(...) self:ixInit(...) self.ixAnimation = 0 end function PANEL:SetFont(font) for _, v in pairs(self:GetCanvas():GetChildren()) do v:SetFont(font) v:SizeToContents() end -- reposition for the new font self:InvalidateLayout(true) self:Open(self.ixX, self.ixY, false, self.ixOwnerPanel) end Override("SetSize") function PANEL:SetSize(width, height) self:ixSetSize(width, height) self.ixTargetHeight = height end Override("PerformLayout") function PANEL:PerformLayout(...) self:ixPerformLayout(...) if (self.ixAnimating) then self.VBar:SetAlpha(0) -- setvisible doesn't seem to work here self:SetTall(self.ixAnimation * self.ixTargetHeight) else self.VBar:SetAlpha(255) end end Override("OnMouseWheeled") function PANEL:OnMouseWheeled(delta) self:ixOnMouseWheeled(delta) -- don't allow the input event to fall through return true end Override("AddOption") function PANEL:AddOption(...) local panel = self:ixAddOption(...) panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black)) panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes return panel end Override("AddSubMenu") function PANEL:AddSubMenu(...) local menu, panel = self:ixAddSubMenu(...) panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black)) panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes return menu, panel end Override("Open") function PANEL:Open(x, y, bSkipAnimation, ownerPanel) self.ixX, self.ixY, self.ixOwnerPanel = x, y, ownerPanel self:ixOpen(x, y, bSkipAnimation, ownerPanel) if (ix.option.Get("disableAnimations")) then return end -- remove pac3 derma menu hooks since animations don't play nicely hook.Remove("CloseDermaMenus", self) hook.Remove("Think", self) self.ixAnimating = true self:CreateAnimation(animationTime, { index = 1, target = {ixAnimation = 1}, easing = "outQuint", Think = function(animation, panel) panel:InvalidateLayout(true) end, OnComplete = function(animation, panel) panel.ixAnimating = nil end }) end Override("Hide") function PANEL:Hide() if (ix.option.Get("disableAnimations")) then self:ixHide() return end self.ixAnimating = true self:SetVisible(true) self:CreateAnimation(animationTime * 0.5, { index = 1, target = {ixAnimation = 0}, easing = "outQuint", Think = function(animation, panel) panel:InvalidateLayout(true) end, OnComplete = function(animation, panel) panel.ixAnimating = false panel:ixHide() end }) end Override("Remove") function PANEL:Remove() if (self.ixRemoving) then return end if (ix.option.Get("disableAnimations")) then self:ixRemove() return end self.ixAnimating = true self.ixRemoving = true self:SetVisible(true) self:CreateAnimation(animationTime * 0.5, { index = 1, target = {ixAnimation = 0}, easing = "outQuint", Think = function(animation, panel) panel:InvalidateLayout(true) end, OnComplete = function(animation, panel) panel:ixRemove() end }) end end) OverridePanel("DComboBox", function() Override("OpenMenu") function PANEL:OpenMenu() self:ixOpenMenu() if (IsValid(self.Menu)) then local _, y = self.Menu:LocalToScreen(self.Menu:GetPos()) self.Menu:SetFont(self:GetFont()) self.Menu:SetMaxHeight(ScrH() - y) end end end) OverridePanel("DScrollPanel", function() Override("ScrollToChild") function PANEL:ScrollToChild(panel) -- docked panels required InvalidateParent in order to retrieve their position correctly if (panel:GetDock() != NODOCK) then panel:InvalidateParent(true) else self:PerformLayout() end local _, y = self.pnlCanvas:GetChildPosition(panel) y = y + panel:GetTall() * 0.5 y = y - self:GetTall() * 0.5 self.VBar:SetScroll(y) end end) ================================================ FILE: gamemode/core/derma/cl_scoreboard.lua ================================================ local rowPaintFunctions = { function(width, height) end, function(width, height) surface.SetDrawColor(30, 30, 30, 25) surface.DrawRect(0, 0, width, height) end } -- character icon -- we can't customize the rendering of ModelImage so we have to do it ourselves local PANEL = {} local BODYGROUPS_EMPTY = "000000000" AccessorFunc(PANEL, "model", "Model", FORCE_STRING) AccessorFunc(PANEL, "bHidden", "Hidden", FORCE_BOOL) function PANEL:Init() self:SetSize(64, 64) self.bodygroups = BODYGROUPS_EMPTY end function PANEL:SetModel(model, skin, bodygroups) model = model:gsub("\\", "/") if (isstring(bodygroups)) then if (bodygroups:len() == 9) then for i = 1, bodygroups:len() do self:SetBodygroup(i, tonumber(bodygroups[i]) or 0) end else self.bodygroups = BODYGROUPS_EMPTY end end self.model = model self.skin = skin self.path = "materials/spawnicons/" .. model:sub(1, #model - 4) .. -- remove extension ((isnumber(skin) and skin > 0) and ("_skin" .. tostring(skin)) or "") .. -- skin number (self.bodygroups != BODYGROUPS_EMPTY and ("_" .. self.bodygroups) or "") .. -- bodygroups ".png" local material = Material(self.path, "smooth") -- we don't have a cached spawnicon texture, so we need to forcefully generate one if (material:IsError()) then self.id = "ixScoreboardIcon" .. self.path self.renderer = self:Add("ModelImage") self.renderer:SetVisible(false) self.renderer:SetModel(model, skin, self.bodygroups) self.renderer:RebuildSpawnIcon() -- this is the only way to get a callback for generated spawn icons, it's bad but it's only done once hook.Add("SpawniconGenerated", self.id, function(lastModel, filePath, modelsLeft) filePath = filePath:gsub("\\", "/"):lower() if (filePath == self.path) then hook.Remove("SpawniconGenerated", self.id) self.material = Material(filePath, "smooth") self.renderer:Remove() end end) else self.material = material end end function PANEL:SetBodygroup(k, v) if (k < 0 or k > 8 or v < 0 or v > 9) then return end self.bodygroups = self.bodygroups:SetChar(k + 1, v) end function PANEL:GetModel() return self.model or "models/error.mdl" end function PANEL:GetSkin() return self.skin or 1 end function PANEL:DoClick() end function PANEL:DoRightClick() end function PANEL:OnMouseReleased(key) if (key == MOUSE_LEFT) then self:DoClick() elseif (key == MOUSE_RIGHT) then self:DoRightClick() end end function PANEL:Paint(width, height) if (!self.material) then return end surface.SetMaterial(self.material) surface.SetDrawColor(self.bHidden and color_black or color_white) surface.DrawTexturedRect(0, 0, width, height) end function PANEL:Remove() if (self.id) then hook.Remove("SpawniconGenerated", self.id) end end vgui.Register("ixScoreboardIcon", PANEL, "Panel") -- player row PANEL = {} AccessorFunc(PANEL, "paintFunction", "BackgroundPaintFunction") function PANEL:Init() self:SetTall(64) self.icon = self:Add("ixScoreboardIcon") self.icon:Dock(LEFT) self.icon.DoRightClick = function() local client = self.player if (!IsValid(client)) then return end local menu = DermaMenu() menu:AddOption(L("viewProfile"), function() client:ShowProfile() end) menu:AddOption(L("copySteamID"), function() SetClipboardText(client:IsBot() and client:EntIndex() or client:SteamID()) end) hook.Run("PopulateScoreboardPlayerMenu", client, menu) menu:Open() end self.icon:SetHelixTooltip(function(tooltip) local client = self.player if (IsValid(self) and IsValid(client)) then ix.hud.PopulatePlayerTooltip(tooltip, client) end end) self.name = self:Add("DLabel") self.name:DockMargin(4, 4, 0, 0) self.name:Dock(TOP) self.name:SetTextColor(color_white) self.name:SetFont("ixGenericFont") self.description = self:Add("DLabel") self.description:DockMargin(5, 0, 0, 0) self.description:Dock(TOP) self.description:SetTextColor(color_white) self.description:SetFont("ixSmallFont") self.paintFunction = rowPaintFunctions[1] self.nextThink = CurTime() + 1 end function PANEL:Update() local client = self.player local model = client:GetModel() local skin = client:GetSkin() local name = client:GetName() local description = hook.Run("GetCharacterDescription", client) or (client:GetCharacter() and client:GetCharacter():GetDescription()) or "" local bRecognize = false local localCharacter = LocalPlayer():GetCharacter() local character = IsValid(self.player) and self.player:GetCharacter() if (localCharacter and character) then bRecognize = hook.Run("IsCharacterRecognized", localCharacter, character:GetID()) or hook.Run("IsPlayerRecognized", self.player) end self.icon:SetHidden(!bRecognize) self:SetZPos(bRecognize and 1 or 2) -- no easy way to check bodygroups so we'll just set them anyway for _, v in pairs(client:GetBodyGroups()) do self.icon:SetBodygroup(v.id, client:GetBodygroup(v.id)) end if (self.icon:GetModel() != model or self.icon:GetSkin() != skin) then self.icon:SetModel(model, skin) self.icon:SetTooltip(nil) end if (self.name:GetText() != name) then self.name:SetText(name) self.name:SizeToContents() end if (self.description:GetText() != description) then self.description:SetText(description) self.description:SizeToContents() end end function PANEL:Think() if (CurTime() >= self.nextThink) then local client = self.player if (!IsValid(client) or !client:GetCharacter() or self.character != client:GetCharacter() or self.team != client:Team()) then self:Remove() self:GetParent():SizeToContents() end self.nextThink = CurTime() + 1 end end function PANEL:SetPlayer(client) self.player = client self.team = client:Team() self.character = client:GetCharacter() self:Update() end function PANEL:Paint(width, height) self.paintFunction(width, height) end vgui.Register("ixScoreboardRow", PANEL, "EditablePanel") -- faction grouping PANEL = {} AccessorFunc(PANEL, "faction", "Faction") function PANEL:Init() self:DockMargin(0, 0, 0, 16) self:SetTall(32) self.nextThink = 0 end function PANEL:AddPlayer(client, index) if (!IsValid(client) or !client:GetCharacter() or hook.Run("ShouldShowPlayerOnScoreboard", client) == false) then return false end local id = index % 2 == 0 and 1 or 2 local panel = self:Add("ixScoreboardRow") panel:SetPlayer(client) panel:Dock(TOP) panel:SetZPos(2) panel:SetBackgroundPaintFunction(rowPaintFunctions[id]) self:SizeToContents() client.ixScoreboardSlot = panel return true end function PANEL:SetFaction(faction) self:SetColor(faction.color) self:SetText(L(faction.name)) self.faction = faction end function PANEL:Update() local faction = self.faction if (team.NumPlayers(faction.index) == 0) then self:SetVisible(false) self:GetParent():InvalidateLayout() else local bHasPlayers for k, v in ipairs(team.GetPlayers(faction.index)) do if (!IsValid(v.ixScoreboardSlot)) then if (self:AddPlayer(v, k)) then bHasPlayers = true end else v.ixScoreboardSlot:Update() bHasPlayers = true end end self:SetVisible(bHasPlayers) end end vgui.Register("ixScoreboardFaction", PANEL, "ixCategoryPanel") -- main scoreboard panel PANEL = {} function PANEL:Init() if (IsValid(ix.gui.scoreboard)) then ix.gui.scoreboard:Remove() end self:Dock(FILL) self.factions = {} self.nextThink = 0 for i = 1, #ix.faction.indices do local faction = ix.faction.indices[i] local panel = self:Add("ixScoreboardFaction") panel:SetFaction(faction) panel:Dock(TOP) self.factions[i] = panel end ix.gui.scoreboard = self end function PANEL:Think() if (CurTime() >= self.nextThink) then for i = 1, #self.factions do local factionPanel = self.factions[i] factionPanel:Update() end self.nextThink = CurTime() + 0.5 end end vgui.Register("ixScoreboard", PANEL, "DScrollPanel") hook.Add("CreateMenuButtons", "ixScoreboard", function(tabs) tabs["scoreboard"] = function(container) container:Add("ixScoreboard") end end) ================================================ FILE: gamemode/core/derma/cl_settings.lua ================================================ local panelMap = { [ix.type.bool] = "ixSettingsRowBool", [ix.type.array] = "ixSettingsRowArray", [ix.type.string] = "ixSettingsRowString", [ix.type.number] = "ixSettingsRowNumber", [ix.type.color] = "ixSettingsRowColor" } local function EmitChange(pitch) LocalPlayer():EmitSound("weapons/ar2/ar2_empty.wav", 75, pitch or 150, 0.25) end -- color setting local PANEL = {} AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) function PANEL:Init() self.color = table.Copy(color_white) self.padding = 4 self.panel = self:Add("Panel") self.panel:SetCursor("hand") self.panel:SetMouseInputEnabled(true) self.panel:Dock(RIGHT) self.panel.Paint = function(panel, width, height) local padding = self.padding surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) surface.DrawRect(0, 0, width, height) surface.SetDrawColor(self.color) surface.DrawRect(padding, padding, width - padding * 2, height - padding * 2) end self.panel.OnMousePressed = function(panel, key) if (key == MOUSE_LEFT) then self:OpenPicker() end end end function PANEL:OpenPicker() if (IsValid(self.picker)) then self.picker:Remove() return end self.picker = vgui.Create("ixSettingsRowColorPicker") self.picker:Attach(self) self.picker:SetValue(self.color) self.picker.OnValueChanged = function(panel) local newColor = panel:GetValue() if (newColor != self.color) then self.color = newColor self:OnValueChanged(newColor) end end self.picker.OnValueUpdated = function(panel) self.color = panel:GetValue() end end function PANEL:SetValue(value) self.color = Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255) end function PANEL:GetValue() return self.color end function PANEL:PerformLayout(width, height) surface.SetFont("ixMenuButtonFont") local totalWidth = surface.GetTextSize("999") self.panel:SetSize(totalWidth + self.padding * 2, height) end vgui.Register("ixSettingsRowColor", PANEL, "ixSettingsRow") -- color setting picker DEFINE_BASECLASS("Panel") PANEL = {} AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL) function PANEL:Init() self.m_bIsMenuComponent = true self.bDeleteSelf = true self.realHeight = 200 self.height = 200 self:SetSize(250, 200) self:DockPadding(4, 4, 4, 4) self.picker = self:Add("DColorMixer") self.picker:Dock(FILL) self.picker.ValueChanged = function() self:OnValueUpdated() end self:MakePopup() RegisterDermaMenuForClose(self) end function PANEL:SetValue(value) self.picker:SetColor(Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255)) end function PANEL:GetValue() return self.picker:GetColor() end function PANEL:Attach(panel) self.attached = panel end function PANEL:Think() local panel = self.attached if (IsValid(panel)) then local width, height = self:GetSize() local x, y = panel:LocalToScreen(0, 0) self:SetPos( math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width), math.Clamp(y + panel:GetTall(), 0, ScrH() - height) ) end end function PANEL:Paint(width, height) surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) surface.DrawRect(0, 0, width, height) end function PANEL:OnValueChanged() end function PANEL:OnValueUpdated() end function PANEL:Remove() if (self.bClosing) then return end self:OnValueChanged() -- @todo open/close animations self.bClosing = true self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) BaseClass.Remove(self) end vgui.Register("ixSettingsRowColorPicker", PANEL, "EditablePanel") -- number setting PANEL = {} function PANEL:Init() self.setting = self:Add("ixNumSlider") self.setting.nextUpdate = 0 self.setting:Dock(RIGHT) self.setting.OnValueChanged = function(panel) self:OnValueChanged(self:GetValue()) end self.setting.OnValueUpdated = function(panel) local fraction = panel:GetFraction() if (fraction == 0) then EmitChange(75) return elseif (fraction == 1) then EmitChange(120) return end if (SysTime() > panel.nextUpdate) then EmitChange(85 + fraction * 15) panel.nextUpdate = SysTime() + 0.05 end end local panel = self.setting:GetLabel() panel:SetCursor("hand") panel:SetMouseInputEnabled(true) panel.OnMousePressed = function(_, key) if (key == MOUSE_LEFT) then self:OpenEntry() end end end function PANEL:OpenEntry() if (IsValid(self.entry)) then self.entry:Remove() return end self.entry = vgui.Create("ixSettingsRowNumberEntry") self.entry:Attach(self) self.entry:SetValue(self:GetValue(), true) self.entry.OnValueChanged = function(panel) local value = math.Round(panel:GetValue(), self:GetDecimals()) if (value != self:GetValue()) then self:SetValue(value, true) self:OnValueChanged(value) end end end function PANEL:SetValue(value, bNoNotify) self.setting:SetValue(value, bNoNotify) end function PANEL:GetValue() return self.setting:GetValue() end function PANEL:SetMin(value) self.setting:SetMin(value) end function PANEL:SetMax(value) self.setting:SetMax(value) end function PANEL:SetDecimals(value) self.setting:SetDecimals(value) end function PANEL:GetDecimals() return self.setting:GetDecimals() end function PANEL:PerformLayout(width, height) self.setting:SetWide(width * 0.5) end vgui.Register("ixSettingsRowNumber", PANEL, "ixSettingsRow") -- number setting entry DEFINE_BASECLASS("Panel") PANEL = {} AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL) function PANEL:Init() surface.SetFont("ixMenuButtonFont") local width, height = surface.GetTextSize("999999") self.m_bIsMenuComponent = true self.bDeleteSelf = true self.realHeight = 200 self.height = 200 self:SetSize(width, height) self:DockPadding(4, 4, 4, 4) self.textEntry = self:Add("ixTextEntry") self.textEntry:SetNumeric(true) self.textEntry:SetFont("ixMenuButtonFont") self.textEntry:Dock(FILL) self.textEntry:RequestFocus() self.textEntry.OnEnter = function() self:Remove() end self:MakePopup() RegisterDermaMenuForClose(self) end function PANEL:SetValue(value, bInitial) value = tostring(value) self.textEntry:SetValue(value) if (bInitial) then self.textEntry:SetCaretPos(value:utf8len()) end end function PANEL:GetValue() return tonumber(self.textEntry:GetValue()) or 0 end function PANEL:Attach(panel) self.attached = panel end function PANEL:Think() local panel = self.attached if (IsValid(panel)) then local width, height = self:GetSize() local x, y = panel:LocalToScreen(0, 0) self:SetPos( math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width), math.Clamp(y + panel:GetTall(), 0, ScrH() - height) ) end end function PANEL:Paint(width, height) surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) surface.DrawRect(0, 0, width, height) end function PANEL:OnValueChanged() end function PANEL:OnValueUpdated() end function PANEL:Remove() if (self.bClosing) then return end self:OnValueChanged() -- @todo open/close animations self.bClosing = true self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) BaseClass.Remove(self) end vgui.Register("ixSettingsRowNumberEntry", PANEL, "EditablePanel") -- string setting PANEL = {} function PANEL:Init() self.setting = self:Add("ixTextEntry") self.setting:Dock(RIGHT) self.setting:SetFont("ixMenuButtonFont") self.setting:SetBackgroundColor(derma.GetColor("DarkerBackground", self)) self.setting.OnEnter = function() self:OnValueChanged(self:GetValue()) end end function PANEL:SetValue(value) self.setting:SetValue(tostring(value)) end function PANEL:GetValue() return self.setting:GetValue() end function PANEL:PerformLayout(width, height) self.setting:SetWide(width * 0.5) end vgui.Register("ixSettingsRowString", PANEL, "ixSettingsRow") -- bool setting PANEL = {} function PANEL:Init() self.setting = self:Add("ixCheckBox") self.setting:Dock(RIGHT) self.setting.DoClick = function(panel) self:OnValueChanged(self:GetValue()) end end function PANEL:SetValue(bValue) bValue = tobool(bValue) self.setting:SetChecked(bValue, true) end function PANEL:GetValue() return self.setting:GetChecked() end vgui.Register("ixSettingsRowBool", PANEL, "ixSettingsRow") -- array setting PANEL = {} function PANEL:Init() self.array = {} self.setting = self:Add("DComboBox") self.setting:Dock(RIGHT) self.setting:SetFont("ixMenuButtonFont") self.setting:SetTextColor(color_white) self.setting.OnSelect = function(panel) self:OnValueChanged(self:GetValue()) panel:SizeToContents() panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice) if (!self.bInitial) then EmitChange() end end end function PANEL:Populate(key, info) if (!isfunction(info.populate)) then ErrorNoHalt(string.format("expected populate function for array option '%s'", key)) return end local entries = info.populate() local i = 1 for k, v in pairs(entries) do self.setting:AddChoice(v, k) self.array[k] = i i = i + 1 end end function PANEL:SetValue(value) self.bInitial = true self.setting:ChooseOptionID(self.array[value]) self.bInitial = false end function PANEL:GetValue() return select(2, self.setting:GetSelected()) end vgui.Register("ixSettingsRowArray", PANEL, "ixSettingsRow") -- settings row PANEL = {} AccessorFunc(PANEL, "backgroundIndex", "BackgroundIndex", FORCE_NUMBER) AccessorFunc(PANEL, "bShowReset", "ShowReset", FORCE_BOOL) function PANEL:Init() self:DockPadding(4, 4, 4, 4) self.text = self:Add("DLabel") self.text:Dock(LEFT) self.text:SetFont("ixMenuButtonFont") self.text:SetExpensiveShadow(1, color_black) self.backgroundIndex = 0 end function PANEL:SetShowReset(value, name, default) value = tobool(value) if (value and !IsValid(self.reset)) then self.reset = self:Add("DButton") self.reset:SetFont("ixSmallTitleIcons") self.reset:SetText("x") self.reset:SetTextColor(ColorAlpha(derma.GetColor("Warning", self), 100)) self.reset:Dock(LEFT) self.reset:DockMargin(4, 0, 0, 0) self.reset:SizeToContents() self.reset.Paint = nil self.reset.DoClick = function() self:OnResetClicked() end self.reset:SetHelixTooltip(function(tooltip) local title = tooltip:AddRow("title") title:SetImportant() title:SetText(L("resetDefault")) title:SetBackgroundColor(derma.GetColor("Warning", self)) title:SizeToContents() local description = tooltip:AddRow("description") description:SetText(L("resetDefaultDescription", tostring(name), tostring(default))) description:SizeToContents() end) elseif (!value and IsValid(self.reset)) then self.reset:Remove() end self.bShowReset = value end function PANEL:Think() if (IsValid(self.reset)) then self.reset:SetVisible(self:IsHovered() or self:IsOurChild(vgui.GetHoveredPanel())) end end function PANEL:OnResetClicked() end function PANEL:GetLabel() return self.text end function PANEL:SetText(text) self.text:SetText(text) self:SizeToContents() end function PANEL:GetText() return self.text:GetText() end -- implemented by row types function PANEL:GetValue() end function PANEL:SetValue(value) end -- meant for array types to populate combo box values function PANEL:Populate(key, info) end -- called when value is changed by user function PANEL:OnValueChanged(newValue) end function PANEL:SizeToContents() local _, top, _, bottom = self:GetDockPadding() self.text:SizeToContents() self:SetTall(self.text:GetTall() + top + bottom) self.ixRealHeight = self:GetTall() self.ixHeight = self.ixRealHeight end function PANEL:Paint(width, height) derma.SkinFunc("PaintSettingsRowBackground", self, width, height) end vgui.Register("ixSettingsRow", PANEL, "EditablePanel") -- settings panel PANEL = {} function PANEL:Init() self:Dock(FILL) self.rows = {} self.categories = {} -- scroll panel DEFINE_BASECLASS("DScrollPanel") self.canvas = self:Add("DScrollPanel") self.canvas:Dock(FILL) self.canvas.PerformLayout = function(panel) BaseClass.PerformLayout(panel) if (!panel.VBar.Enabled) then panel.pnlCanvas:SetWide(panel:GetWide() - panel.VBar:GetWide()) end end end function PANEL:GetRowPanelName(type) return panelMap[type] or "ixSettingsRow" end function PANEL:AddCategory(name) local panel = self.categories[name] if (!IsValid(panel)) then panel = self.canvas:Add("ixCategoryPanel") panel:SetText(name) panel:Dock(TOP) panel:DockMargin(0, 8, 0, 0) self.categories[name] = panel return panel end end function PANEL:AddRow(type, category) category = self.categories[category] local id = panelMap[type] if (!id) then ErrorNoHalt("attempted to create row with unimplemented type '" .. tostring(ix.type[type]) .. "'\n") id = "ixSettingsRow" end local panel = (IsValid(category) and category or self.canvas):Add(id) panel:Dock(TOP) panel:SetBackgroundIndex(#self.rows % 2) self.rows[#self.rows + 1] = panel return panel end function PANEL:GetRows() return self.rows end function PANEL:Clear() for _, v in ipairs(self.rows) do if (IsValid(v)) then v:Remove() end end self.rows = {} end function PANEL:SetSearchEnabled(bValue) if (!bValue) then if (IsValid(self.searchEntry)) then self.searchEntry:Remove() end return end -- search entry self.searchEntry = self:Add("ixIconTextEntry") self.searchEntry:Dock(TOP) self.searchEntry:SetEnterAllowed(false) self.searchEntry.OnChange = function(entry) self:FilterRows(entry:GetValue()) end end function PANEL:FilterRows(query) query = string.PatternSafe(query:lower()) local bEmpty = query == "" for categoryName, category in pairs(self.categories) do category.size = 0 category:CreateAnimation(0.5, { index = 21, target = {size = 1}, Think = function(animation, panel) panel:SizeToContents() end }) for _, row in ipairs(category:GetChildren()) do local bFound = bEmpty or row:GetText():lower():find(query) or categoryName:lower():find(query) row:SetVisible(true) row:CreateAnimation(0.5, { index = 21, target = {ixHeight = bFound and row.ixRealHeight or 0}, easing = "outQuint", Think = function(animation, panel) panel:SetTall(bFound and math.min(panel.ixHeight + 2, panel.ixRealHeight) or math.max(panel.ixHeight - 2, 0)) end, OnComplete = function(animation, panel) panel:SetVisible(bFound) -- need this so categories are sized properly when animations are disabled - there is no guaranteed order -- that animations will think so we SizeToContents here. putting it here will result in redundant calls but -- I guess we have the performance to spare if (ix.option.Get("disableAnimations", false)) then category:SizeToContents() end end }) end end end function PANEL:Paint(width, height) end function PANEL:SizeToContents() for _, v in pairs(self.categories) do v:SizeToContents() end end vgui.Register("ixSettings", PANEL, "Panel") hook.Add("CreateMenuButtons", "ixSettings", function(tabs) tabs["settings"] = { PopulateTabButton = function(info, button) local menu = ix.gui.menu if (!IsValid(menu)) then return end DEFINE_BASECLASS("ixMenuButton") button:SetZPos(9999) button.Paint = function(panel, width, height) BaseClass.Paint(panel, width, height) surface.SetDrawColor(255, 255, 255, 33) surface.DrawRect(0, 0, width, 1) end end, Create = function(info, container) local panel = container:Add("ixSettings") panel:SetSearchEnabled(true) for category, options in SortedPairs(ix.option.GetAllByCategories(true)) do category = L(category) panel:AddCategory(category) -- sort options by language phrase rather than the key table.sort(options, function(a, b) return L(a.phrase) < L(b.phrase) end) for _, data in pairs(options) do local key = data.key local row = panel:AddRow(data.type, category) local value = ix.util.SanitizeType(data.type, ix.option.Get(key)) row:SetText(L(data.phrase)) row:Populate(key, data) -- type-specific properties if (data.type == ix.type.number) then row:SetMin(data.min or 0) row:SetMax(data.max or 10) row:SetDecimals(data.decimals or 0) end row:SetValue(value, true) row:SetShowReset(value != data.default, key, data.default) row.OnValueChanged = function() local newValue = row:GetValue() row:SetShowReset(newValue != data.default, key, data.default) ix.option.Set(key, newValue) end row.OnResetClicked = function() row:SetShowReset(false) row:SetValue(data.default, true) ix.option.Set(key, data.default) end row:GetLabel():SetHelixTooltip(function(tooltip) local title = tooltip:AddRow("name") title:SetImportant() title:SetText(key) title:SizeToContents() title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5)) local description = tooltip:AddRow("description") description:SetText(L(data.description)) description:SizeToContents() end) end end panel:SizeToContents() container.panel = panel end, OnSelected = function(info, container) container.panel.searchEntry:RequestFocus() end } end) ================================================ FILE: gamemode/core/derma/cl_shipment.lua ================================================ local PANEL = {} function PANEL:Init() self:SetSize(460, 360) self:SetTitle(L"shipment") self:Center() self:MakePopup() self.scroll = self:Add("DScrollPanel") self.scroll:Dock(FILL) self.list = self.scroll:Add("DListLayout") self.list:Dock(FILL) end function PANEL:SetItems(entity, items) self.entity = entity self.items = true self.itemPanels = {} for k, v in SortedPairs(items) do local itemTable = ix.item.list[k] if (itemTable) then local item = self.list:Add("DPanel") item:SetTall(36) item:Dock(TOP) item:DockMargin(4, 4, 4, 0) item.icon = item:Add("SpawnIcon") item.icon:SetPos(2, 2) item.icon:SetSize(32, 32) item.icon:SetModel(itemTable:GetModel()) item.icon:SetHelixTooltip(function(tooltip) ix.hud.PopulateItemTooltip(tooltip, itemTable) end) item.quantity = item.icon:Add("DLabel") item.quantity:SetSize(32, 32) item.quantity:SetContentAlignment(3) item.quantity:SetTextInset(0, 0) item.quantity:SetText(v) item.quantity:SetFont("DermaDefaultBold") item.quantity:SetExpensiveShadow(1, Color(0, 0, 0, 150)) item.name = item:Add("DLabel") item.name:SetPos(38, 0) item.name:SetSize(200, 36) item.name:SetFont("ixSmallFont") item.name:SetText(L(itemTable.name)) item.name:SetContentAlignment(4) item.name:SetTextColor(color_white) item.take = item:Add("DButton") item.take:Dock(RIGHT) item.take:SetText(L"take") item.take:SetWide(48) item.take:DockMargin(3, 3, 3, 3) item.take:SetTextColor(color_white) item.take.DoClick = function(this) net.Start("ixShipmentUse") net.WriteString(k) net.WriteBool(false) net.SendToServer() items[k] = items[k] - 1 item.quantity:SetText(items[k]) if (items[k] <= 0) then item:Remove() items[k] = nil end if (table.IsEmpty(items)) then self:Remove() end end item.drop = item:Add("DButton") item.drop:Dock(RIGHT) item.drop:SetText(L"drop") item.drop:SetWide(48) item.drop:DockMargin(3, 3, 0, 3) item.drop:SetTextColor(color_white) item.drop.DoClick = function(this) net.Start("ixShipmentUse") net.WriteString(k) net.WriteBool(true) net.SendToServer() items[k] = items[k] - 1 item.quantity:SetText(items[k]) if (items[k] <= 0) then item:Remove() end end self.itemPanels[k] = item end end end function PANEL:Close() net.Start("ixShipmentClose") net.SendToServer() self:Remove() end function PANEL:Think() if (self.items and !IsValid(self.entity)) then self:Remove() end end vgui.Register("ixShipment", PANEL, "DFrame") ================================================ FILE: gamemode/core/derma/cl_spawnicon.lua ================================================ DEFINE_BASECLASS("DModelPanel") local PANEL = {} function PANEL:Init() self.defaultEyeTarget = Vector(0, 0, 64) self:SetHidden(false) for i = 0, 5 do if (i == 1 or i == 5) then self:SetDirectionalLight(i, Color(155, 155, 155)) else self:SetDirectionalLight(i, Color(255, 255, 255)) end end end function PANEL:SetModel(model, skin, hidden) BaseClass.SetModel(self, model) local entity = self.Entity if (skin) then entity:SetSkin(skin) end local sequence = entity:SelectWeightedSequence(ACT_IDLE) if (sequence <= 0) then sequence = entity:LookupSequence("idle_unarmed") end if (sequence > 0) then entity:ResetSequence(sequence) else local found = false for _, v in ipairs(entity:GetSequenceList()) do if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then entity:ResetSequence(v) found = true break end end if (!found) then entity:ResetSequence(4) end end local data = PositionSpawnIcon(entity, entity:GetPos()) if (data) then self:SetFOV(data.fov) self:SetCamPos(data.origin) self:SetLookAng(data.angles) end entity:SetIK(false) entity:SetEyeTarget(self.defaultEyeTarget) end function PANEL:SetHidden(hidden) if (hidden) then self:SetAmbientLight(color_black) self:SetColor(Color(0, 0, 0)) for i = 0, 5 do self:SetDirectionalLight(i, color_black) end else self:SetAmbientLight(Color(20, 20, 20)) self:SetAlpha(255) for i = 0, 5 do if (i == 1 or i == 5) then self:SetDirectionalLight(i, Color(155, 155, 155)) else self:SetDirectionalLight(i, Color(255, 255, 255)) end end end end function PANEL:LayoutEntity() self:RunAnimation() end function PANEL:OnMousePressed() if (self.DoClick) then self:DoClick() end end vgui.Register("ixSpawnIcon", PANEL, "DModelPanel") ================================================ FILE: gamemode/core/derma/cl_storage.lua ================================================ local PANEL = {} AccessorFunc(PANEL, "money", "Money", FORCE_NUMBER) function PANEL:Init() self:DockPadding(1, 1, 1, 1) self:SetTall(64) self:Dock(BOTTOM) self.moneyLabel = self:Add("DLabel") self.moneyLabel:Dock(TOP) self.moneyLabel:SetFont("ixGenericFont") self.moneyLabel:SetText("") self.moneyLabel:SetTextInset(2, 0) self.moneyLabel:SizeToContents() self.moneyLabel.Paint = function(panel, width, height) derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ix.config.Get("color")) end self.amountEntry = self:Add("ixTextEntry") self.amountEntry:Dock(FILL) self.amountEntry:SetFont("ixGenericFont") self.amountEntry:SetNumeric(true) self.amountEntry:SetValue("0") self.transferButton = self:Add("DButton") self.transferButton:SetFont("ixIconsMedium") self:SetLeft(false) self.transferButton.DoClick = function() local amount = math.max(0, math.Round(tonumber(self.amountEntry:GetValue()) or 0)) self.amountEntry:SetValue("0") if (amount != 0) then self:OnTransfer(amount) end end self.bNoBackgroundBlur = true end function PANEL:SetLeft(bValue) if (bValue) then self.transferButton:Dock(LEFT) self.transferButton:SetText("s") else self.transferButton:Dock(RIGHT) self.transferButton:SetText("t") end end function PANEL:SetMoney(money) local name = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "") self.money = math.max(math.Round(tonumber(money) or 0), 0) self.moneyLabel:SetText(string.format("%s: %d", name, money)) end function PANEL:OnTransfer(amount) end function PANEL:Paint(width, height) derma.SkinFunc("PaintBaseFrame", self, width, height) end vgui.Register("ixStorageMoney", PANEL, "EditablePanel") DEFINE_BASECLASS("Panel") PANEL = {} AccessorFunc(PANEL, "fadeTime", "FadeTime", FORCE_NUMBER) AccessorFunc(PANEL, "frameMargin", "FrameMargin", FORCE_NUMBER) AccessorFunc(PANEL, "storageID", "StorageID", FORCE_NUMBER) function PANEL:Init() if (IsValid(ix.gui.openedStorage)) then ix.gui.openedStorage:Remove() end ix.gui.openedStorage = self self:SetSize(ScrW(), ScrH()) self:SetPos(0, 0) self:SetFadeTime(0.25) self:SetFrameMargin(4) self.storageInventory = self:Add("ixInventory") self.storageInventory.bNoBackgroundBlur = true self.storageInventory:ShowCloseButton(true) self.storageInventory:SetTitle("Storage") self.storageInventory.Close = function(this) net.Start("ixStorageClose") net.SendToServer() self:Remove() end self.storageMoney = self.storageInventory:Add("ixStorageMoney") self.storageMoney:SetVisible(false) self.storageMoney.OnTransfer = function(_, amount) net.Start("ixStorageMoneyTake") net.WriteUInt(self.storageID, 32) net.WriteUInt(amount, 32) net.SendToServer() end ix.gui.inv1 = self:Add("ixInventory") ix.gui.inv1.bNoBackgroundBlur = true ix.gui.inv1:ShowCloseButton(true) ix.gui.inv1.Close = function(this) net.Start("ixStorageClose") net.SendToServer() self:Remove() end self.localMoney = ix.gui.inv1:Add("ixStorageMoney") self.localMoney:SetVisible(false) self.localMoney:SetLeft(true) self.localMoney.OnTransfer = function(_, amount) net.Start("ixStorageMoneyGive") net.WriteUInt(self.storageID, 32) net.WriteUInt(amount, 32) net.SendToServer() end self:SetAlpha(0) self:AlphaTo(255, self:GetFadeTime()) self.storageInventory:MakePopup() ix.gui.inv1:MakePopup() end function PANEL:OnChildAdded(panel) panel:SetPaintedManually(true) end function PANEL:SetLocalInventory(inventory) if (IsValid(ix.gui.inv1) and !IsValid(ix.gui.menu)) then ix.gui.inv1:SetInventory(inventory) ix.gui.inv1:SetPos(self:GetWide() / 2 + self:GetFrameMargin() / 2, self:GetTall() / 2 - ix.gui.inv1:GetTall() / 2) end end function PANEL:SetLocalMoney(money) if (!self.localMoney:IsVisible()) then self.localMoney:SetVisible(true) ix.gui.inv1:SetTall(ix.gui.inv1:GetTall() + self.localMoney:GetTall() + 2) end self.localMoney:SetMoney(money) end function PANEL:SetStorageTitle(title) self.storageInventory:SetTitle(title) end function PANEL:SetStorageInventory(inventory) self.storageInventory:SetInventory(inventory) self.storageInventory:SetPos( self:GetWide() / 2 - self.storageInventory:GetWide() - 2, self:GetTall() / 2 - self.storageInventory:GetTall() / 2 ) ix.gui["inv" .. inventory:GetID()] = self.storageInventory end function PANEL:SetStorageMoney(money) if (!self.storageMoney:IsVisible()) then self.storageMoney:SetVisible(true) self.storageInventory:SetTall(self.storageInventory:GetTall() + self.storageMoney:GetTall() + 2) end self.storageMoney:SetMoney(money) end function PANEL:Paint(width, height) ix.util.DrawBlurAt(0, 0, width, height) for _, v in ipairs(self:GetChildren()) do v:PaintManual() end end function PANEL:Remove() self:SetAlpha(255) self:AlphaTo(0, self:GetFadeTime(), 0, function() BaseClass.Remove(self) end) end function PANEL:OnRemove() if (!IsValid(ix.gui.menu)) then self.storageInventory:Remove() ix.gui.inv1:Remove() end end vgui.Register("ixStorageView", PANEL, "Panel") ================================================ FILE: gamemode/core/derma/cl_subpanel.lua ================================================ local DEFAULT_PADDING = ScreenScale(32) local DEFAULT_ANIMATION_TIME = 1 local DEFAULT_SUBPANEL_ANIMATION_TIME = 0.5 -- parent subpanel local PANEL = {} function PANEL:Init() local parent = self:GetParent() local padding = parent.GetPadding and parent:GetPadding() or DEFAULT_PADDING self:SetSize(parent:GetWide() - (padding * 2), parent:GetTall() - (padding * 2)) self:Center() end function PANEL:SetTitle(text, bNoTranslation, bNoUpper) if (text == nil) then if (IsValid(self.title)) then self.title:Remove() end return elseif (!IsValid(self.title)) then self.title = self:Add("DLabel") self.title:SetFont("ixTitleFont") self.title:SizeToContents() self.title:SetTextColor(ix.config.Get("color") or color_white) self.title:Dock(TOP) end local newText = bNoTranslation and text or L(text) newText = bNoUpper and newText or newText:utf8upper() self.title:SetText(newText) self.title:SizeToContents() end function PANEL:SetLeftPanel(panel) self.left = panel end function PANEL:GetLeftPanel() return self.left end function PANEL:SetRightPanel(panel) self.right = panel end function PANEL:GetRightPanel() return self.right end function PANEL:OnSetActive() end vgui.Register("ixSubpanel", PANEL, "EditablePanel") -- subpanel parent DEFINE_BASECLASS("EditablePanel") PANEL = {} AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) AccessorFunc(PANEL, "subpanelAnimationTime", "SubpanelAnimationTime", FORCE_NUMBER) AccessorFunc(PANEL, "leftOffset", "LeftOffset", FORCE_NUMBER) function PANEL:Init() self.subpanels = {} self.childPanels = {} self.currentSubpanelX = DEFAULT_PADDING self.targetSubpanelX = DEFAULT_PADDING self.padding = DEFAULT_PADDING self.leftOffset = 0 self.animationTime = DEFAULT_ANIMATION_TIME self.subpanelAnimationTime = DEFAULT_SUBPANEL_ANIMATION_TIME end function PANEL:SetPadding(amount, bSetDockPadding) self.currentSubpanelX = amount self.targetSubpanelX = amount self.padding = amount if (bSetDockPadding) then self:DockPadding(amount, amount, amount, amount) end end function PANEL:Add(name) local panel = BaseClass.Add(self, name) if (panel.SetPaintedManually) then panel:SetPaintedManually(true) self.childPanels[#self.childPanels + 1] = panel end return panel end function PANEL:AddSubpanel(name) local id = #self.subpanels + 1 local panel = BaseClass.Add(self, "ixSubpanel") panel.subpanelName = name panel.subpanelID = id panel:SetTitle(name) self.subpanels[id] = panel self:SetupSubpanelReferences() return panel end function PANEL:SetupSubpanelReferences() local lastPanel for i = 1, #self.subpanels do local panel = self.subpanels[i] local nextPanel = self.subpanels[i + 1] if (IsValid(lastPanel)) then lastPanel:SetRightPanel(panel) panel:SetLeftPanel(lastPanel) end if (IsValid(nextPanel)) then panel:SetRightPanel(nextPanel) end lastPanel = panel end end function PANEL:SetSubpanelPos(id, x) local currentPanel = self.subpanels[id] if (!currentPanel) then return end local _, oldY = currentPanel:GetPos() currentPanel:SetPos(x, oldY) -- traverse left while (IsValid(currentPanel)) do local left = currentPanel:GetLeftPanel() if (IsValid(left)) then left:MoveLeftOf(currentPanel, self.padding + self.leftOffset) end currentPanel = left end currentPanel = self.subpanels[id] -- traverse right while (IsValid(currentPanel)) do local right = currentPanel:GetRightPanel() if (IsValid(right)) then right:MoveRightOf(currentPanel, self.padding) end currentPanel = right end end function PANEL:SetActiveSubpanel(id, length) if (isstring(id)) then for i = 1, #self.subpanels do if (self.subpanels[i].subpanelName == id) then id = i break end end end local activePanel = self.subpanels[id] if (!activePanel) then return false end if (length == 0 or !self.activeSubpanel) then self:SetSubpanelPos(id, self.padding + self.leftOffset) else local x, _ = activePanel:GetPos() local target = self.targetSubpanelX + self.leftOffset self.currentSubpanelX = x + self.padding + self.leftOffset self:CreateAnimation(length or self.subpanelAnimationTime, { index = 420, target = {currentSubpanelX = target}, easing = "outQuint", Think = function(animation, panel) panel:SetSubpanelPos(id, panel.currentSubpanelX) end, OnComplete = function(animation, panel) panel:SetSubpanelPos(id, target) end }) end self.activeSubpanel = id activePanel:OnSetActive() return true end function PANEL:GetSubpanel(id) return self.subpanels[id] end function PANEL:GetActiveSubpanel() return self.subpanels[self.activeSubpanel] end function PANEL:GetActiveSubpanelID() return self.activeSubpanel end function PANEL:Slide(direction, length, callback, bIgnoreConfig) local _, height = self:GetParent():GetSize() local x, _ = self:GetPos() local targetY = direction == "up" and 0 or height self:SetVisible(true) if (length == 0) then self:SetPos(x, targetY) else length = length or self.animationTime self.currentY = direction == "up" and height or 0 self:CreateAnimation(length or self.animationTime, { index = -1, target = {currentY = targetY}, easing = "outExpo", bIgnoreConfig = bIgnoreConfig, Think = function(animation, panel) local currentX, _ = panel:GetPos() panel:SetPos(currentX, panel.currentY) end, OnComplete = function(animation, panel) if (direction == "down") then panel:SetVisible(false) end if (callback) then callback(panel) end end }) end end function PANEL:SlideUp(...) self:SetMouseInputEnabled(true) self:SetKeyboardInputEnabled(true) self:OnSlideUp() self:Slide("up", ...) end function PANEL:SlideDown(...) self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) self:OnSlideDown() self:Slide("down", ...) end function PANEL:OnSlideUp() end function PANEL:OnSlideDown() end function PANEL:Paint(width, height) for i = 1, #self.childPanels do if not (IsValid(self.childPanels[i])) then continue end self.childPanels[i]:PaintManual() end end function PANEL:PaintSubpanels(width, height) for i = 1, #self.subpanels do if not (IsValid(self.subpanels[i])) then continue end self.subpanels[i]:PaintManual() end end -- ???? PANEL.Remove = BaseClass.Remove vgui.Register("ixSubpanelParent", PANEL, "EditablePanel") ================================================ FILE: gamemode/core/derma/cl_tooltip.lua ================================================ --- Text container for `ixTooltip`. -- Rows are the main way of interacting with `ixTooltip`s. These derive from -- [DLabel](https://wiki.garrysmod.com/page/Category:DLabel) panels, which means that making use of this panel -- will be largely the same as any DLabel panel. -- @panel ixTooltipRow local animationTime = 1 -- panel meta do local PANEL = FindMetaTable("Panel") local ixChangeTooltip = ChangeTooltip local ixRemoveTooltip = RemoveTooltip local tooltip local lastHover function PANEL:SetHelixTooltip(callback) self:SetMouseInputEnabled(true) self.ixTooltip = callback end function ChangeTooltip(panel, ...) -- luacheck: globals ChangeTooltip if (!panel.ixTooltip) then return ixChangeTooltip(panel, ...) end RemoveTooltip() timer.Create("ixTooltip", 0.1, 1, function() if (!IsValid(panel) or lastHover != panel) then return end tooltip = vgui.Create("ixTooltip") panel.ixTooltip(tooltip) tooltip:SizeToContents() end) lastHover = panel end function RemoveTooltip() -- luacheck: globals RemoveTooltip if (IsValid(tooltip)) then tooltip:Remove() tooltip = nil end timer.Remove("ixTooltip") lastHover = nil return ixRemoveTooltip() end end DEFINE_BASECLASS("DLabel") local PANEL = {} AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER) AccessorFunc(PANEL, "bNoMinimal", "MinimalHidden", FORCE_BOOL) function PANEL:Init() self:SetFont("ixSmallFont") self:SetText(L("unknown")) self:SetTextColor(color_white) self:SetTextInset(4, 0) self:SetContentAlignment(4) self:Dock(TOP) self.maxWidth = ScrW() * 0.2 self.bNoMinimal = false self.bMinimal = false end --- Whether or not this tooltip row should be displayed in a minimal format. This usually means no background and/or -- smaller font. You probably won't need this if you're using regular `ixTooltipRow` panels, but you should take into -- account if you're creating your own panels that derive from `ixTooltipRow`. -- @realm client -- @treturn bool True if this tooltip row should be displayed in a minimal format function PANEL:IsMinimal() return self.bMinimal end --- Sets this row to be more prominent with a larger font and more noticable background color. This should usually -- be used once per tooltip as a title row. For example, item tooltips have one "important" row consisting of the -- item's name. Note that this function is a fire-and-forget function; you cannot revert a row back to it's regular state -- unless you set the font/colors manually. -- @realm client function PANEL:SetImportant() self:SetFont("ixSmallTitleFont") self:SetExpensiveShadow(1, color_black) self:SetBackgroundColor(ix.config.Get("color")) end --- Sets the background color of this row. This should be used sparingly to avoid overwhelming players with a -- bunch of different colors that could convey different meanings. -- @realm client -- @color color New color of the background. The alpha is clamped to 100-255 to ensure visibility function PANEL:SetBackgroundColor(color) color = table.Copy(color) color.a = math.min(color.a or 255, 100) self.backgroundColor = color end --- Resizes this panel to fit its contents. This should be called after setting the text. -- @realm client function PANEL:SizeToContents() local contentWidth, contentHeight = self:GetContentSize() contentWidth = contentWidth + 4 contentHeight = contentHeight + 4 if (contentWidth > self.maxWidth) then self:SetWide(self.maxWidth - 4) -- to account for text inset self:SetTextInset(4, 0) self:SetWrap(true) self:SizeToContentsY() else self:SetSize(contentWidth, contentHeight) end end --- Resizes the height of this panel to fit its contents. -- @internal -- @realm client function PANEL:SizeToContentsY() BaseClass.SizeToContentsY(self) self:SetTall(self:GetTall() + 4) end --- Called when the background of this row should be painted. This will paint the background with the -- `DrawImportantBackground` function set in the skin by default. -- @realm client -- @number width Width of the panel -- @number height Height of the panel function PANEL:PaintBackground(width, height) if (self.backgroundColor) then derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor) end end --- Called when the foreground of this row should be painted. If you are overriding this in a subclassed panel, -- make sure you call `ixTooltipRow:PaintBackground` at the *beginning* of your function to make its style -- consistent with the rest of the framework. -- @realm client -- @number width Width of the panel -- @number height Height of the panel function PANEL:Paint(width, height) self:PaintBackground(width, height) end vgui.Register("ixTooltipRow", PANEL, "DLabel") --- Generic information panel. -- Tooltips are used extensively throughout Helix: for item information, character displays, entity status, etc. -- The tooltip system can be used on any panel or entity you would like to show standardized information for. Tooltips -- consist of the parent container panel (`ixTooltip`), which is filled with rows of information (usually -- `ixTooltipRow`, but can be any docked panel if non-text information needs to be shown, like an item's size). -- -- Tooltips can be added to panel with `panel:SetHelixTooltip()`. An example taken from the scoreboard: -- panel:SetHelixTooltip(function(tooltip) -- local name = tooltip:AddRow("name") -- name:SetImportant() -- name:SetText(client:SteamName()) -- name:SetBackgroundColor(team.GetColor(client:Team())) -- name:SizeToContents() -- -- tooltip:SizeToContents() -- end) -- @panel ixTooltip DEFINE_BASECLASS("Panel") PANEL = {} AccessorFunc(PANEL, "entity", "Entity") AccessorFunc(PANEL, "mousePadding", "MousePadding", FORCE_NUMBER) AccessorFunc(PANEL, "bDrawArrow", "DrawArrow", FORCE_BOOL) AccessorFunc(PANEL, "arrowColor", "ArrowColor") AccessorFunc(PANEL, "bHideArrowWhenRaised", "HideArrowWhenRaised", FORCE_BOOL) AccessorFunc(PANEL, "bArrowFollowEntity", "ArrowFollowEntity", FORCE_BOOL) function PANEL:Init() self.fraction = 0 self.mousePadding = 16 self.arrowColor = ix.config.Get("color") self.bHideArrowWhenRaised = true self.bArrowFollowEntity = true self.bMinimal = false self.lastX, self.lastY = self:GetCursorPosition() self.arrowX, self.arrowY = ScrW() * 0.5, ScrH() * 0.5 self:SetAlpha(0) self:SetSize(0, 0) self:SetDrawOnTop(true) self:SetMouseInputEnabled(false) self:CreateAnimation(animationTime, { index = 1, target = {fraction = 1}, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.fraction * 255) end }) end --- Whether or not this tooltip should be displayed in a minimal format. -- @realm client -- @treturn bool True if this tooltip should be displayed in a minimal format -- @see ixTooltipRow:IsMinimal function PANEL:IsMinimal() return self.bMinimal end -- ensure all children are painted manually function PANEL:Add(...) local panel = BaseClass.Add(self, ...) panel:SetPaintedManually(true) return panel end --- Creates a new `ixTooltipRow` panel and adds it to the bottom of this tooltip. -- @realm client -- @string id Name of the new row. This is used to reorder rows if needed -- @treturn panel Created row function PANEL:AddRow(id) local panel = self:Add("ixTooltipRow") panel.id = id panel:SetZPos(#self:GetChildren() * 10) return panel end --- Creates a new `ixTooltipRow` and adds it after the row with the given `id`. The order of the rows is set via -- setting the Z position of the panels, as this is how VGUI handles ordering with docked panels. -- @realm client -- @string after Name of the row to insert after -- @string id Name of the newly created row -- @treturn panel Created row function PANEL:AddRowAfter(after, id) local panel = self:AddRow(id) after = self:GetRow(after) if (!IsValid(after)) then return panel end panel:SetZPos(after:GetZPos() + 1) return panel end --- Sets the entity associated with this tooltip. Note that this function is not how you get entities to show tooltips. -- @internal -- @realm client -- @entity entity Entity to associate with this tooltip function PANEL:SetEntity(entity) if (!IsValid(entity)) then self.bEntity = false return end -- don't show entity tooltips if we have an entity menu open if (IsValid(ix.menu.panel)) then self:Remove() return end if (entity:IsPlayer()) then local character = entity:GetCharacter() if (character) then -- we want to group things that will most likely have backgrounds (e.g name/health status) hook.Run("PopulateImportantCharacterInfo", entity, character, self) hook.Run("PopulateCharacterInfo", entity, character, self) end else if (entity.OnPopulateEntityInfo) then entity:OnPopulateEntityInfo(self) else hook.Run("PopulateEntityInfo", entity, self) end end self:SizeToContents() self.entity = entity self.bEntity = true end function PANEL:PaintUnder(width, height) end function PANEL:Paint(width, height) self:PaintUnder() -- directional arrow self.bRaised = LocalPlayer():IsWepRaised() if (!self.bClosing) then if (self.bEntity and IsValid(self.entity) and self.bArrowFollowEntity) then local entity = self.entity local position = select(1, entity:GetBonePosition(entity:LookupBone("ValveBiped.Bip01_Spine") or -1)) or entity:LocalToWorld(entity:OBBCenter()) position = position:ToScreen() self.arrowX = math.Clamp(position.x, 0, ScrW()) self.arrowY = math.Clamp(position.y, 0, ScrH()) end end -- arrow if (self.bDrawArrow or (self.bDrawArrow and self.bRaised and !self.bHideArrowWhenRaised)) then local x, y = self:ScreenToLocal(self.arrowX, self.arrowY) DisableClipping(true) surface.SetDrawColor(self.arrowColor) surface.DrawLine(0, 0, x * self.fraction, y * self.fraction) surface.DrawRect((x - 2) * self.fraction, (y - 2) * self.fraction, 4, 4) DisableClipping(false) end -- contents local x, y = self:GetPos() render.SetScissorRect(x, y, x + width * self.fraction, y + height, true) derma.SkinFunc("PaintTooltipBackground", self, width, height) for _, v in ipairs(self:GetChildren()) do if (IsValid(v)) then v:PaintManual() end end render.SetScissorRect(0, 0, 0, 0, false) end --- Returns the current position of the mouse cursor on the screen. -- @realm client -- @treturn number X position of cursor -- @treturn number Y position of cursor function PANEL:GetCursorPosition() local width, height = self:GetSize() local mouseX, mouseY = gui.MousePos() return math.Clamp(mouseX + self.mousePadding, 0, ScrW() - width), math.Clamp(mouseY, 0, ScrH() - height) end function PANEL:Think() if (!self.bEntity) then if (!vgui.CursorVisible()) then self:SetPos(self.lastX, self.lastY) -- if the cursor isn't visible then we don't really need the tooltip to be shown if (!self.bClosing) then self:Remove() end else local newX, newY = self:GetCursorPosition() self:SetPos(newX, newY) self.lastX, self.lastY = newX, newY end self:MoveToFront() -- dragging a panel w/ tooltip will push the tooltip beneath even the menu panel(???) elseif (IsValid(self.entity) and !self.bClosing) then if (self.bRaised) then self:SetPos( ScrW() * 0.5 - self:GetWide() * 0.5, math.min(ScrH() * 0.5 + self:GetTall() + 32, ScrH() - self:GetTall()) ) else local entity = self.entity local min, max = entity:GetRotatedAABB(entity:OBBMins() * 0.5, entity:OBBMaxs() * 0.5) min = entity:LocalToWorld(min):ToScreen().x max = entity:LocalToWorld(max):ToScreen().x self:SetPos( math.Clamp(math.max(min, max), ScrW() * 0.5 + 64, ScrW() - self:GetWide()), ScrH() * 0.5 - self:GetTall() * 0.5 ) end end end --- Returns an `ixTooltipRow` corresponding to the given name. -- @realm client -- @string id Name of the row -- @treturn[1] panel Corresponding row -- @treturn[2] nil If the row doesn't exist function PANEL:GetRow(id) for _, v in ipairs(self:GetChildren()) do if (IsValid(v) and v.id == id) then return v end end end --- Resizes the tooltip to fit all of the child panels. You should always call this after you are done -- adding all of your rows. -- @realm client function PANEL:SizeToContents() local height = 0 local width = 0 for _, v in ipairs(self:GetChildren()) do if (v:GetWide() > width) then width = v:GetWide() end height = height + v:GetTall() end self:SetSize(width, height) end function PANEL:Remove() if (self.bClosing) then return end self.bClosing = true self:CreateAnimation(animationTime * 0.5, { target = {fraction = 0}, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(panel.fraction * 255) end, OnComplete = function(animation, panel) BaseClass.Remove(panel) end }) end vgui.Register("ixTooltip", PANEL, "Panel") -- legacy tooltip row PANEL = {} function PANEL:Init() self.bMinimal = true self.ixAlpha = 0 -- to avoid conflicts if we're animating a non-tooltip panel self:SetExpensiveShadow(1, color_black) self:SetContentAlignment(5) end function PANEL:SetImportant() self:SetFont("ixMinimalTitleFont") self:SetBackgroundColor(ix.config.Get("color")) end -- background color will affect text instead in minimal tooltips function PANEL:SetBackgroundColor(color) color = table.Copy(color) color.a = math.min(color.a or 255, 100) self:SetTextColor(color) self.backgroundColor = color end function PANEL:PaintBackground() end vgui.Register("ixTooltipMinimalRow", PANEL, "ixTooltipRow") -- legacy tooltip DEFINE_BASECLASS("ixTooltip") PANEL = {} function PANEL:Init() self.bMinimal = true -- we don't want to animate the alpha since children will handle their own animation, but we want to keep the fraction -- for the background to animate self:CreateAnimation(animationTime, { index = 1, target = {fraction = 1}, easing = "outQuint", }) self:SetAlpha(255) end -- we don't need the children to be painted manually function PANEL:Add(...) local panel = BaseClass.Add(self, ...) panel:SetPaintedManually(false) return panel end function PANEL:AddRow(id) local panel = self:Add("ixTooltipMinimalRow") panel.id = id panel:SetZPos(#self:GetChildren() * 10) return panel end function PANEL:Paint(width, height) self:PaintUnder() derma.SkinFunc("PaintTooltipMinimalBackground", self, width, height) end function PANEL:Think() end function PANEL:SizeToContents() -- remove any panels that shouldn't be shown in a minimal tooltip for _, v in ipairs(self:GetChildren()) do if (v.bNoMinimal) then v:Remove() end end BaseClass.SizeToContents(self) self:SetPos(ScrW() * 0.5 - self:GetWide() * 0.5, ScrH() * 0.5 + self.mousePadding) -- we create animation here since this is the only function that usually gets called after all the rows are populated local children = self:GetChildren() -- sort by z index so we can animate them in order table.sort(children, function(a, b) return a:GetZPos() < b:GetZPos() end) local i = 1 local count = table.Count(children) for _, v in ipairs(children) do v.ixAlpha = v.ixAlpha or 0 v:CreateAnimation((animationTime / count) * i, { easing = "inSine", target = {ixAlpha = 255}, Think = function(animation, panel) panel:SetAlpha(panel.ixAlpha) end }) i = i + 1 end end DEFINE_BASECLASS("Panel") function PANEL:Remove() if (self.bClosing) then return end self.bClosing = true -- we create animation here since this is the only function that usually gets called after all the rows are populated local children = self:GetChildren() -- sort by z index so we can animate them in order table.sort(children, function(a, b) return a:GetZPos() > b:GetZPos() end) local duration = animationTime * 0.5 local i = 1 local count = table.Count(children) for _, v in ipairs(children) do v.ixAlpha = v.ixAlpha or 255 v:CreateAnimation(duration / count * i, { target = {ixAlpha = 0}, Think = function(animation, panel) panel:SetAlpha(panel.ixAlpha) end }) i = i + 1 end self:CreateAnimation(duration, { target = {fraction = 0}, OnComplete = function(animation, panel) BaseClass.Remove(panel) end }) end vgui.Register("ixTooltipMinimal", PANEL, "ixTooltip") ================================================ FILE: gamemode/core/hooks/cl_hooks.lua ================================================ function GM:ForceDermaSkin() return "helix" end function GM:ScoreboardShow() if (LocalPlayer():GetCharacter()) then vgui.Create("ixMenu") end end function GM:ScoreboardHide() end function GM:LoadFonts(font, genericFont) surface.CreateFont("ix3D2DFont", { font = font, size = 128, extended = true, weight = 100 }) surface.CreateFont("ix3D2DMediumFont", { font = font, size = 48, extended = true, weight = 100 }) surface.CreateFont("ix3D2DSmallFont", { font = font, size = 24, extended = true, weight = 400 }) surface.CreateFont("ixTitleFont", { font = font, size = ScreenScale(30), extended = true, weight = 100 }) surface.CreateFont("ixSubTitleFont", { font = font, size = ScreenScale(16), extended = true, weight = 100 }) surface.CreateFont("ixMenuMiniFont", { font = "Roboto", size = math.max(ScreenScale(4), 18), weight = 300, }) surface.CreateFont("ixMenuButtonFont", { font = "Roboto Th", size = ScreenScale(14), extended = true, weight = 100 }) surface.CreateFont("ixMenuButtonFontSmall", { font = "Roboto Th", size = ScreenScale(10), extended = true, weight = 100 }) surface.CreateFont("ixMenuButtonFontThick", { font = "Roboto", size = ScreenScale(14), extended = true, weight = 300 }) surface.CreateFont("ixMenuButtonLabelFont", { font = "Roboto Th", size = 28, extended = true, weight = 100 }) surface.CreateFont("ixMenuButtonHugeFont", { font = "Roboto Th", size = ScreenScale(24), extended = true, weight = 100 }) surface.CreateFont("ixToolTipText", { font = font, size = 20, extended = true, weight = 500 }) surface.CreateFont("ixMonoSmallFont", { font = "Consolas", size = 12, extended = true, weight = 800 }) surface.CreateFont("ixMonoMediumFont", { font = "Consolas", size = 22, extended = true, weight = 800 }) -- The more readable font. font = genericFont surface.CreateFont("ixBigFont", { font = font, size = 36, extended = true, weight = 1000 }) surface.CreateFont("ixMediumFont", { font = font, size = 25, extended = true, weight = 1000 }) surface.CreateFont("ixNoticeFont", { font = font, size = math.max(ScreenScale(8), 18), weight = 100, extended = true, antialias = true }) surface.CreateFont("ixMediumLightFont", { font = font, size = 25, extended = true, weight = 200 }) surface.CreateFont("ixMediumLightBlurFont", { font = font, size = 25, extended = true, weight = 200, blursize = 4 }) surface.CreateFont("ixGenericFont", { font = font, size = 20, extended = true, weight = 1000 }) surface.CreateFont("ixChatFont", { font = font, size = math.max(ScreenScale(7), 17) * ix.option.Get("chatFontScale", 1), extended = true, weight = 600, antialias = true }) surface.CreateFont("ixChatFontItalics", { font = font, size = math.max(ScreenScale(7), 17) * ix.option.Get("chatFontScale", 1), extended = true, weight = 600, antialias = true, italic = true }) surface.CreateFont("ixSmallTitleFont", { font = "Roboto Th", size = math.max(ScreenScale(12), 24), extended = true, weight = 100 }) surface.CreateFont("ixMinimalTitleFont", { font = "Roboto", size = math.max(ScreenScale(8), 22), extended = true, weight = 800 }) surface.CreateFont("ixSmallFont", { font = font, size = math.max(ScreenScale(6), 17), extended = true, weight = 500 }) surface.CreateFont("ixItemDescFont", { font = font, size = math.max(ScreenScale(6), 17), extended = true, shadow = true, weight = 500 }) surface.CreateFont("ixSmallBoldFont", { font = font, size = math.max(ScreenScale(8), 20), extended = true, weight = 800 }) surface.CreateFont("ixItemBoldFont", { font = font, shadow = true, size = math.max(ScreenScale(8), 20), extended = true, weight = 800 }) -- Introduction fancy font. font = "Roboto Th" surface.CreateFont("ixIntroTitleFont", { font = font, size = math.min(ScreenScale(128), 128), extended = true, weight = 100 }) surface.CreateFont("ixIntroTitleBlurFont", { font = font, size = math.min(ScreenScale(128), 128), extended = true, weight = 100, blursize = 4 }) surface.CreateFont("ixIntroSubtitleFont", { font = font, size = ScreenScale(24), extended = true, weight = 100 }) surface.CreateFont("ixIntroSmallFont", { font = font, size = ScreenScale(14), extended = true, weight = 100 }) surface.CreateFont("ixIconsSmall", { font = "fontello", size = 22, extended = true, weight = 500 }) surface.CreateFont("ixSmallTitleIcons", { font = "fontello", size = math.max(ScreenScale(11), 23), extended = true, weight = 100 }) surface.CreateFont("ixIconsMedium", { font = "fontello", extended = true, size = 28, weight = 500 }) surface.CreateFont("ixIconsMenuButton", { font = "fontello", size = ScreenScale(14), extended = true, weight = 100 }) surface.CreateFont("ixIconsBig", { font = "fontello", extended = true, size = 48, weight = 500 }) end function GM:OnCharacterMenuCreated(panel) if (IsValid(ix.gui.notices)) then ix.gui.notices:Clear() end end local LOWERED_ANGLES = Angle(30, 0, -25) function GM:CalcViewModelView(weapon, viewModel, oldEyePos, oldEyeAngles, eyePos, eyeAngles) if (!IsValid(weapon)) then return end local client = LocalPlayer() local bWepRaised = client:IsWepRaised() -- update tween if the raised state is out of date if (client.ixWasWeaponRaised != bWepRaised) then local fraction = bWepRaised and 0 or 1 client.ixRaisedFraction = 1 - fraction client.ixRaisedTween = ix.tween.new(0.75, client, { ixRaisedFraction = fraction }, "outQuint") client.ixWasWeaponRaised = bWepRaised end local fraction = client.ixRaisedFraction local rotation = weapon.LowerAngles or LOWERED_ANGLES if (ix.option.Get("altLower", true) and weapon.LowerAngles2) then rotation = weapon.LowerAngles2 end eyeAngles:RotateAroundAxis(eyeAngles:Up(), rotation.p * fraction) eyeAngles:RotateAroundAxis(eyeAngles:Forward(), rotation.y * fraction) eyeAngles:RotateAroundAxis(eyeAngles:Right(), rotation.r * fraction) viewModel:SetAngles(eyeAngles) return self.BaseClass:CalcViewModelView(weapon, viewModel, oldEyePos, oldEyeAngles, eyePos, eyeAngles) end function GM:LoadIntro() if (!IsValid(ix.gui.intro)) then vgui.Create("ixIntro") end end function GM:CharacterLoaded() local menu = ix.gui.characterMenu if (IsValid(menu)) then menu:Close((LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()) and true or nil) end end function GM:InitializedConfig() local color = ix.config.Get("color") hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) hook.Run("ColorSchemeChanged", color) if (!ix.config.loaded and !IsValid(ix.gui.loading)) then local loader = vgui.Create("EditablePanel") loader:ParentToHUD() loader:Dock(FILL) loader.Paint = function(this, w, h) surface.SetDrawColor(0, 0, 0) surface.DrawRect(0, 0, w, h) end local statusLabel = loader:Add("DLabel") statusLabel:Dock(FILL) statusLabel:SetText(L"loading") statusLabel:SetFont("ixTitleFont") statusLabel:SetContentAlignment(5) statusLabel:SetTextColor(color_white) timer.Simple(5, function() if (IsValid(ix.gui.loading)) then local fault = GetNetVar("dbError") if (fault) then statusLabel:SetText(fault and L"dbError" or L"loading") local label = loader:Add("DLabel") label:DockMargin(0, 64, 0, 0) label:Dock(TOP) label:SetFont("ixSubTitleFont") label:SetText(fault) label:SetContentAlignment(5) label:SizeToContentsY() label:SetTextColor(Color(255, 50, 50)) end end end) ix.gui.loading = loader ix.config.loaded = true if (ix.config.Get("intro", true) and ix.option.Get("showIntro", true)) then hook.Run("LoadIntro") end end end function GM:InitPostEntity() ix.joinTime = RealTime() - 0.9716 ix.option.Sync() ix.gui.bars = vgui.Create("ixInfoBarManager") end function GM:NetworkEntityCreated(entity) if (entity:IsPlayer()) then entity:SetIK(false) -- we've just discovered a new player, so we need to update their animation state if (entity != LocalPlayer()) then -- we don't need to call the PlayerWeaponChanged hook here since it'll be handled below, -- when this player's weapon has been discovered hook.Run("PlayerModelChanged", entity, entity:GetModel()) end elseif (entity:IsWeapon()) then local owner = entity:GetOwner() if (IsValid(owner) and owner:IsPlayer() and entity == owner:GetActiveWeapon()) then hook.Run("PlayerWeaponChanged", owner, entity) end end end local vignette = ix.util.GetMaterial("helix/gui/vignette.png") local vignetteAlphaGoal = 0 local vignetteAlphaDelta = 0 local vignetteTraceHeight = Vector(0, 0, 768) local blurGoal = 0 local blurDelta = 0 local hasVignetteMaterial = !vignette:IsError() timer.Create("ixVignetteChecker", 1, 0, function() local client = LocalPlayer() if (IsValid(client)) then local data = {} data.start = client:GetPos() data.endpos = data.start + vignetteTraceHeight data.filter = client local trace = util.TraceLine(data) -- this timer could run before InitPostEntity is called, so we have to check for the validity of the trace table if (trace and trace.Hit) then vignetteAlphaGoal = 80 else vignetteAlphaGoal = 0 end end end) function GM:CalcView(client, origin, angles, fov) local view = self.BaseClass:CalcView(client, origin, angles, fov) or {} local entity = Entity(client:GetLocalVar("ragdoll", 0)) local ragdoll = IsValid(client:GetRagdollEntity()) and client:GetRagdollEntity() or entity if ((!client:ShouldDrawLocalPlayer() and IsValid(entity) and entity:IsRagdoll()) or (!LocalPlayer():Alive() and IsValid(ragdoll))) then local ent = LocalPlayer():Alive() and entity or ragdoll local index = ent:LookupAttachment("eyes") if (index) then local data = ent:GetAttachment(index) if (data) then view.origin = data.Pos view.angles = data.Ang end return view end end local menu = ix.gui.menu local entityMenu = ix.menu.panel if (IsValid(menu) and menu:IsVisible() and menu:GetCharacterOverview()) then local newOrigin, newAngles, newFOV, bDrawPlayer = menu:GetOverviewInfo(origin, angles, fov) view.drawviewer = bDrawPlayer view.fov = newFOV view.origin = newOrigin view.angles = newAngles elseif (IsValid(entityMenu)) then view.angles = entityMenu:GetOverviewInfo(origin, angles) end return view end local hookRun = hook.Run do local aimLength = 0.35 local aimTime = 0 local aimEntity local lastEntity local lastTrace = {} timer.Create("ixCheckTargetEntity", 0.1, 0, function() local client = LocalPlayer() local time = SysTime() if (!IsValid(client)) then return end local character = client:GetCharacter() if (!character) then return end lastTrace.start = client:GetShootPos() lastTrace.endpos = lastTrace.start + client:GetAimVector(client) * 160 lastTrace.filter = client lastTrace.mask = MASK_SHOT_HULL lastEntity = util.TraceHull(lastTrace).Entity if (lastEntity != aimEntity) then aimTime = time + aimLength aimEntity = lastEntity end local panel = ix.gui.entityInfo local bShouldShow = time >= aimTime and (!IsValid(ix.gui.menu) or ix.gui.menu.bClosing) and (!IsValid(ix.gui.characterMenu) or ix.gui.characterMenu.bClosing) local bShouldPopulate = lastEntity.OnShouldPopulateEntityInfo and lastEntity:OnShouldPopulateEntityInfo() or true if (bShouldShow and IsValid(lastEntity) and hookRun("ShouldPopulateEntityInfo", lastEntity) != false and (lastEntity.PopulateEntityInfo or bShouldPopulate)) then if (!IsValid(panel) or (IsValid(panel) and panel:GetEntity() != lastEntity)) then if (IsValid(ix.gui.entityInfo)) then ix.gui.entityInfo:Remove() end local infoPanel = vgui.Create(ix.option.Get("minimalTooltips", false) and "ixTooltipMinimal" or "ixTooltip") local entityPlayer = lastEntity:GetNetVar("player") if (entityPlayer) then infoPanel:SetEntity(entityPlayer) infoPanel.entity = lastEntity else infoPanel:SetEntity(lastEntity) end infoPanel:SetDrawArrow(true) ix.gui.entityInfo = infoPanel end elseif (IsValid(panel)) then panel:Remove() end end) end local mathApproach = math.Approach local surface = surface function GM:HUDPaintBackground() local client = LocalPlayer() if (!client:GetCharacter()) then return end local frameTime = FrameTime() local scrW, scrH = ScrW(), ScrH() if (hasVignetteMaterial and ix.config.Get("vignette")) then vignetteAlphaDelta = mathApproach(vignetteAlphaDelta, vignetteAlphaGoal, frameTime * 30) surface.SetDrawColor(0, 0, 0, 175 + vignetteAlphaDelta) surface.SetMaterial(vignette) surface.DrawTexturedRect(0, 0, scrW, scrH) end blurGoal = client:GetLocalVar("blur", 0) + (hookRun("AdjustBlurAmount", blurGoal) or 0) if (blurDelta != blurGoal) then blurDelta = mathApproach(blurDelta, blurGoal, frameTime * 20) end if (blurDelta > 0 and !client:ShouldDrawLocalPlayer()) then ix.util.DrawBlurAt(0, 0, scrW, scrH, blurDelta) end self.BaseClass:PaintWorldTips() local weapon = client:GetActiveWeapon() if (IsValid(weapon) and hook.Run("CanDrawAmmoHUD", weapon) != false and weapon.DrawAmmo != false) then local clip = weapon:Clip1() local clipMax = weapon:GetMaxClip1() local count = client:GetAmmoCount(weapon:GetPrimaryAmmoType()) local secondary = client:GetAmmoCount(weapon:GetSecondaryAmmoType()) local x, y = scrW - 80, scrH - 80 if (secondary > 0) then ix.util.DrawBlurAt(x, y, 64, 64) surface.SetDrawColor(255, 255, 255, 5) surface.DrawRect(x, y, 64, 64) surface.SetDrawColor(255, 255, 255, 3) surface.DrawOutlinedRect(x, y, 64, 64) ix.util.DrawText(secondary, x + 32, y + 32, nil, 1, 1, "ixBigFont") end if (weapon:GetClass() != "weapon_slam" and clip > 0 or count > 0) then x = x - (secondary > 0 and 144 or 64) ix.util.DrawBlurAt(x, y, 128, 64) surface.SetDrawColor(255, 255, 255, 5) surface.DrawRect(x, y, 128, 64) surface.SetDrawColor(255, 255, 255, 3) surface.DrawOutlinedRect(x, y, 128, 64) ix.util.DrawText((clip == -1 or clipMax == -1) and count or clip.."/"..count, x + 64, y + 32, nil, 1, 1, "ixBigFont") end end if (client:GetLocalVar("restricted") and !client:GetLocalVar("restrictNoMsg")) then ix.util.DrawText(L"restricted", scrW * 0.5, scrH * 0.33, nil, 1, 1, "ixBigFont") end end function GM:PostDrawOpaqueRenderables(bDepth, bSkybox) if (bDepth or bSkybox or #ix.blurRenderQueue == 0) then return end ix.util.ResetStencilValues() render.SetStencilEnable(true) render.SetStencilWriteMask(27) render.SetStencilTestMask(27) render.SetStencilFailOperation(STENCILOPERATION_KEEP) render.SetStencilZFailOperation(STENCILOPERATION_KEEP) render.SetStencilPassOperation(STENCILOPERATION_REPLACE) render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) render.SetStencilReferenceValue(27) for i = 1, #ix.blurRenderQueue do ix.blurRenderQueue[i]() end render.SetStencilReferenceValue(34) render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) render.SetStencilPassOperation(STENCILOPERATION_REPLACE) render.SetStencilReferenceValue(27) cam.Start2D() ix.util.DrawBlurAt(0, 0, ScrW(), ScrH()) cam.End2D() render.SetStencilEnable(false) ix.blurRenderQueue = {} end function GM:PostDrawHUD() cam.Start2D() ix.hud.DrawAll() if (!IsValid(ix.gui.deathScreen) and (!IsValid(ix.gui.characterMenu) or ix.gui.characterMenu:IsClosing())) then ix.bar.DrawAction() end cam.End2D() end function GM:ShouldPopulateEntityInfo(entity) local client = LocalPlayer() local ragdoll = Entity(client:GetLocalVar("ragdoll", 0)) local entityPlayer = entity:GetNetVar("player") if (vgui.CursorVisible() or !client:Alive() or IsValid(ragdoll) or entity == client or entityPlayer == client) then return false end end local injTextTable = { [.3] = {"injMajor", Color(192, 57, 43)}, [.6] = {"injLittle", Color(231, 76, 60)}, } function GM:GetInjuredText(client) local health = client:Health() for k, v in pairs(injTextTable) do if ((health / client:GetMaxHealth()) < k) then return v[1], v[2] end end end function GM:PopulateImportantCharacterInfo(client, character, container) local color = team.GetColor(client:Team()) container:SetArrowColor(color) -- name local name = container:AddRow("name") name:SetImportant() name:SetText(hookRun("GetCharacterName", client) or character:GetName()) name:SetBackgroundColor(color) name:SizeToContents() -- injured text local injureText, injureTextColor = hookRun("GetInjuredText", client) if (injureText) then local injure = container:AddRow("injureText") injure:SetText(L(injureText)) injure:SetBackgroundColor(injureTextColor) injure:SizeToContents() end end function GM:PopulateCharacterInfo(client, character, container) -- description local descriptionText = character:GetDescription() descriptionText = (descriptionText:utf8len() > 128 and string.format("%s...", descriptionText:utf8sub(1, 125)) or descriptionText) if (descriptionText != "") then local description = container:AddRow("description") description:SetText(descriptionText) description:SizeToContents() end end function GM:KeyRelease(client, key) if (!IsFirstTimePredicted()) then return end if (key == IN_USE) then if (!ix.menu.IsOpen()) then local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local entity = util.TraceLine(data).Entity if (IsValid(entity) and isfunction(entity.GetEntityMenu)) then hook.Run("ShowEntityMenu", entity) end end timer.Remove("ixItemUse") client.ixInteractionTarget = nil client.ixInteractionStartTime = nil end end function GM:PlayerBindPress(client, bind, pressed) bind = bind:lower() if (bind:find("use") and pressed) then local pickupTime = ix.config.Get("itemPickupTime", 0.5) if (pickupTime > 0) then local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local entity = util.TraceLine(data).Entity if (IsValid(entity) and entity.ShowPlayerInteraction and !ix.menu.IsOpen()) then client.ixInteractionTarget = entity client.ixInteractionStartTime = SysTime() timer.Create("ixItemUse", pickupTime, 1, function() client.ixInteractionTarget = nil client.ixInteractionStartTime = nil end) end end elseif (bind:find("jump")) then local entity = Entity(client:GetLocalVar("ragdoll", 0)) if (IsValid(entity)) then ix.command.Send("CharGetUp") end elseif (bind:find("speed") and client:KeyDown(IN_WALK) and pressed) then if (LocalPlayer():Crouching()) then RunConsoleCommand("-duck") else RunConsoleCommand("+duck") end end end function GM:CreateMove(command) if ((IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) or (IsValid(ix.gui.menu) and !ix.gui.menu.bClosing and ix.gui.menu:GetActiveTab() == "you")) then command:ClearButtons() command:ClearMovement() end end -- Called when use has been pressed on an item. function GM:ShowEntityMenu(entity) local options = entity:GetEntityMenu(LocalPlayer()) if (istable(options) and !table.IsEmpty(options)) then ix.menu.Open(options, entity) end end local hidden = {} hidden["CHudHealth"] = true hidden["CHudBattery"] = true hidden["CHudAmmo"] = true hidden["CHudSecondaryAmmo"] = true hidden["CHudCrosshair"] = true hidden["CHudHistoryResource"] = true hidden["CHudPoisonDamageIndicator"] = true hidden["CHudSquadStatus"] = true hidden["CHUDQuickInfo"] = true function GM:HUDShouldDraw(element) if (hidden[element]) then return false end return true end function GM:ShouldDrawLocalPlayer(client) if (IsValid(ix.gui.characterMenu) and ix.gui.characterMenu:IsVisible()) then return false end end function GM:PostProcessPermitted(class) return false end function GM:RenderScreenspaceEffects() local menu = ix.gui.menu if (IsValid(menu) and menu:GetCharacterOverview()) then local client = LocalPlayer() local target = client:GetObserverTarget() local weapon = client:GetActiveWeapon() cam.Start3D() ix.util.ResetStencilValues() render.SetStencilEnable(true) render.SuppressEngineLighting(true) cam.IgnoreZ(true) render.SetColorModulation(1, 1, 1) render.SetStencilWriteMask(28) render.SetStencilTestMask(28) render.SetStencilReferenceValue(28) render.SetStencilCompareFunction(STENCIL_ALWAYS) render.SetStencilPassOperation(STENCIL_REPLACE) render.SetStencilFailOperation(STENCIL_KEEP) render.SetStencilZFailOperation(STENCIL_KEEP) if (IsValid(target)) then target:DrawModel() else client:DrawModel() end if (IsValid(weapon)) then weapon:DrawModel() end hook.Run("DrawCharacterOverview") render.SetStencilCompareFunction(STENCIL_NOTEQUAL) render.SetStencilPassOperation(STENCIL_KEEP) cam.Start2D() derma.SkinFunc("DrawCharacterStatusBackground", menu, menu.overviewFraction) cam.End2D() cam.IgnoreZ(false) render.SuppressEngineLighting(false) render.SetStencilEnable(false) cam.End3D() end end function GM:ShowPlayerOptions(client, options) options["viewProfile"] = {"icon16/user.png", function() if (IsValid(client)) then client:ShowProfile() end end} options["Copy Steam ID"] = {"icon16/user.png", function() if (IsValid(client)) then SetClipboardText(client:SteamID()) end end} end function GM:DrawHelixModelView(panel, ent) if (ent.weapon and IsValid(ent.weapon)) then ent.weapon:DrawModel() end end net.Receive("ixStringRequest", function() local time = net.ReadUInt(32) local title, subTitle = net.ReadString(), net.ReadString() local default = net.ReadString() if (title:sub(1, 1) == "@") then title = L(title:sub(2)) end if (subTitle:sub(1, 1) == "@") then subTitle = L(subTitle:sub(2)) end Derma_StringRequest(title, subTitle, default or "", function(text) net.Start("ixStringRequest") net.WriteUInt(time, 32) net.WriteString(text) net.SendToServer() end) end) net.Receive("ixPlayerDeath", function() if (IsValid(ix.gui.deathScreen)) then ix.gui.deathScreen:Remove() end ix.gui.deathScreen = vgui.Create("ixDeathScreen") end) function GM:Think() local client = LocalPlayer() if (IsValid(client) and client:Alive() and client.ixRaisedTween) then client.ixRaisedTween:update(FrameTime()) end end function GM:ScreenResolutionChanged(oldW, oldH) hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) if (IsValid(ix.gui.notices)) then ix.gui.notices:Remove() ix.gui.notices = vgui.Create("ixNoticeManager") end if (IsValid(ix.gui.bars)) then ix.gui.bars:Remove() ix.gui.bars = vgui.Create("ixInfoBarManager") end end function GM:DrawDeathNotice() return false end function GM:HUDAmmoPickedUp() return false end function GM:HUDDrawPickupHistory() return false end function GM:HUDDrawTargetID() return false end function GM:BuildBusinessMenu() if (!ix.config.Get("allowBusiness", true)) then return false end end gameevent.Listen("player_spawn") hook.Add("player_spawn", "ixPlayerSpawn", function(data) local client = Player(data.userid) if (IsValid(client)) then -- GetBoneName returns __INVALIDBONE__ for everything the first time you use it, so we'll force an update to make them valid client:SetupBones() client:SetIK(false) if (client == LocalPlayer() and (IsValid(ix.gui.deathScreen) and !ix.gui.deathScreen:IsClosing())) then ix.gui.deathScreen:Close() end end end) ================================================ FILE: gamemode/core/hooks/sh_hooks.lua ================================================ function GM:PlayerNoClip(client) return client:IsAdmin() end -- luacheck: globals HOLDTYPE_TRANSLATOR HOLDTYPE_TRANSLATOR = {} HOLDTYPE_TRANSLATOR[""] = "normal" HOLDTYPE_TRANSLATOR["physgun"] = "smg" HOLDTYPE_TRANSLATOR["ar2"] = "smg" HOLDTYPE_TRANSLATOR["crossbow"] = "shotgun" HOLDTYPE_TRANSLATOR["rpg"] = "shotgun" HOLDTYPE_TRANSLATOR["slam"] = "normal" HOLDTYPE_TRANSLATOR["grenade"] = "grenade" HOLDTYPE_TRANSLATOR["fist"] = "normal" HOLDTYPE_TRANSLATOR["melee2"] = "melee" HOLDTYPE_TRANSLATOR["passive"] = "normal" HOLDTYPE_TRANSLATOR["knife"] = "melee" HOLDTYPE_TRANSLATOR["duel"] = "pistol" HOLDTYPE_TRANSLATOR["camera"] = "smg" HOLDTYPE_TRANSLATOR["magic"] = "normal" HOLDTYPE_TRANSLATOR["revolver"] = "pistol" -- luacheck: globals PLAYER_HOLDTYPE_TRANSLATOR PLAYER_HOLDTYPE_TRANSLATOR = {} PLAYER_HOLDTYPE_TRANSLATOR[""] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["fist"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["pistol"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["grenade"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["melee"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["slam"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["melee2"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["passive"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["knife"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["duel"] = "normal" PLAYER_HOLDTYPE_TRANSLATOR["bugbait"] = "normal" local PLAYER_HOLDTYPE_TRANSLATOR = PLAYER_HOLDTYPE_TRANSLATOR local HOLDTYPE_TRANSLATOR = HOLDTYPE_TRANSLATOR local animationFixOffset = Vector(16.5438, -0.1642, -20.5493) function GM:TranslateActivity(client, act) local clientInfo = client:GetTable() local modelClass = clientInfo.ixAnimModelClass or "player" local bRaised = client:IsWepRaised() if (modelClass == "player") then local weapon = client:GetActiveWeapon() local bAlwaysRaised = ix.config.Get("weaponAlwaysRaised") weapon = IsValid(weapon) and weapon or nil if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then local model = string.lower(client:GetModel()) if (string.find(model, "zombie")) then local tree = ix.anim.zombie if (string.find(model, "fast")) then tree = ix.anim.fastZombie end if (tree[act]) then return tree[act] end end local holdType = weapon and (weapon.HoldType or weapon:GetHoldType()) or "normal" if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then holdType = PLAYER_HOLDTYPE_TRANSLATOR[holdType] or "passive" end local tree = ix.anim.player[holdType] if (tree and tree[act]) then if (isstring(tree[act])) then clientInfo.CalcSeqOverride = client:LookupSequence(tree[act]) return else return tree[act] end end end return self.BaseClass:TranslateActivity(client, act) end if (clientInfo.ixAnimTable) then local glide = clientInfo.ixAnimGlide if (client:InVehicle()) then act = clientInfo.ixAnimTable[1] local fixVector = clientInfo.ixAnimTable[2] if (isvector(fixVector)) then client:SetLocalPos(animationFixOffset) end if (isstring(act)) then clientInfo.CalcSeqOverride = client:LookupSequence(act) else return act end elseif (client:OnGround()) then if (clientInfo.ixAnimTable[act]) then local act2 = clientInfo.ixAnimTable[act][bRaised and 2 or 1] if (isstring(act2)) then clientInfo.CalcSeqOverride = client:LookupSequence(act2) else return act2 end end elseif (glide) then if (isstring(glide)) then clientInfo.CalcSeqOverride = client:LookupSequence(glide) else return clientInfo.ixAnimGlide end end end end function GM:CanPlayerUseBusiness(client, uniqueID) if (!ix.config.Get("allowBusiness", true)) then return false end local itemTable = ix.item.list[uniqueID] if (!client:GetCharacter()) then return false end if (itemTable.noBusiness) then return false end if (itemTable.factions) then local allowed = false if (istable(itemTable.factions)) then for _, v in pairs(itemTable.factions) do if (client:Team() == v) then allowed = true break end end elseif (client:Team() != itemTable.factions) then allowed = false end if (!allowed) then return false end end if (itemTable.classes) then local allowed = false if (istable(itemTable.classes)) then for _, v in pairs(itemTable.classes) do if (client:GetCharacter():GetClass() == v) then allowed = true break end end elseif (client:GetCharacter():GetClass() == itemTable.classes) then allowed = true end if (!allowed) then return false end end if (itemTable.flag) then if (!client:GetCharacter():HasFlags(itemTable.flag)) then return false end end return true end function GM:DoAnimationEvent(client, event, data) local class = client.ixAnimModelClass if (class == "player") then return self.BaseClass:DoAnimationEvent(client, event, data) else local weapon = client:GetActiveWeapon() if (IsValid(weapon)) then local animation = client.ixAnimTable if (event == PLAYERANIMEVENT_ATTACK_PRIMARY) then client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true) return ACT_VM_PRIMARYATTACK elseif (event == PLAYERANIMEVENT_ATTACK_SECONDARY) then client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true) return ACT_VM_SECONDARYATTACK elseif (event == PLAYERANIMEVENT_RELOAD) then client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.reload or ACT_GESTURE_RELOAD_SMG1, true) return ACT_INVALID elseif (event == PLAYERANIMEVENT_JUMP) then client:AnimRestartMainSequence() return ACT_INVALID elseif (event == PLAYERANIMEVENT_CANCEL_RELOAD) then client:AnimResetGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD) return ACT_INVALID end end end return ACT_INVALID end function GM:EntityEmitSound(data) if (data.Entity.ixIsMuted) then return false end end function GM:EntityRemoved(entity) if (SERVER) then entity:ClearNetVars() elseif (entity:IsWeapon()) then local owner = entity:GetOwner() -- GetActiveWeapon is the player's new weapon at this point so we'll assume -- that the player switched away from this weapon if (IsValid(owner) and owner:IsPlayer()) then hook.Run("PlayerWeaponChanged", owner, owner:GetActiveWeapon()) end end end local function UpdatePlayerHoldType(client, weapon) weapon = weapon or client:GetActiveWeapon() local holdType = "normal" if (IsValid(weapon)) then holdType = weapon.HoldType or weapon:GetHoldType() holdType = HOLDTYPE_TRANSLATOR[holdType] or holdType end client.ixAnimHoldType = holdType end local function UpdateAnimationTable(client, vehicle) local baseTable = ix.anim[client.ixAnimModelClass] or {} if (IsValid(client) and IsValid(vehicle)) then local vehicleClass = vehicle:IsChair() and "chair" or vehicle:GetClass() if (baseTable.vehicle and baseTable.vehicle[vehicleClass]) then client.ixAnimTable = baseTable.vehicle[vehicleClass] else client.ixAnimTable = baseTable.normal[ACT_MP_CROUCH_IDLE] end else client.ixAnimTable = baseTable[client.ixAnimHoldType] end client.ixAnimGlide = baseTable["glide"] end function GM:PlayerWeaponChanged(client, weapon) UpdatePlayerHoldType(client, weapon) UpdateAnimationTable(client) if (CLIENT) then return end -- update weapon raise state if (weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()]) then client:SetWepRaised(true, weapon) return elseif (weapon.IsAlwaysLowered or weapon.NeverRaised) then client:SetWepRaised(false, weapon) return end -- If the player has been forced to have their weapon lowered. if (client:IsRestricted()) then client:SetWepRaised(false, weapon) return end -- Let the config decide before actual results. if (ix.config.Get("weaponAlwaysRaised")) then client:SetWepRaised(true, weapon) return end client:SetWepRaised(false, weapon) end function GM:PlayerSwitchWeapon(client, oldWeapon, weapon) if (!IsFirstTimePredicted()) then return end -- the player switched weapon themself (i.e not through SelectWeapon), so we have to network it here if (SERVER) then net.Start("PlayerSelectWeapon") net.WriteEntity(client) net.WriteString(weapon:GetClass()) net.Broadcast() end hook.Run("PlayerWeaponChanged", client, weapon) end function GM:PlayerModelChanged(client, model) client.ixAnimModelClass = ix.anim.GetModelClass(model) UpdateAnimationTable(client) end do local vectorAngle = FindMetaTable("Vector").Angle local normalizeAngle = math.NormalizeAngle function GM:CalcMainActivity(client, velocity) local clientInfo = client:GetTable() local forcedSequence = client:GetNetVar("forcedSequence") if (forcedSequence) then if (client:GetSequence() != forcedSequence) then client:SetCycle(0) end return -1, forcedSequence end client:SetPoseParameter("move_yaw", normalizeAngle(vectorAngle(velocity)[2] - client:EyeAngles()[2])) local sequenceOverride = clientInfo.CalcSeqOverride clientInfo.CalcSeqOverride = -1 clientInfo.CalcIdeal = ACT_MP_STAND_IDLE -- we could call the baseclass function, but it's faster to do it this way local BaseClass = self.BaseClass if (BaseClass:HandlePlayerNoClipping(client, velocity) or BaseClass:HandlePlayerDriving(client) or BaseClass:HandlePlayerVaulting(client, velocity) or BaseClass:HandlePlayerJumping(client, velocity) or BaseClass:HandlePlayerSwimming(client, velocity) or BaseClass:HandlePlayerDucking(client, velocity)) then -- luacheck: ignore 542 else local length = velocity:Length2DSqr() if (length > 22500) then clientInfo.CalcIdeal = ACT_MP_RUN elseif (length > 0.25) then clientInfo.CalcIdeal = ACT_MP_WALK end end clientInfo.m_bWasOnGround = client:OnGround() clientInfo.m_bWasNoclipping = (client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) return clientInfo.CalcIdeal, sequenceOverride or clientInfo.CalcSeqOverride or -1 end end do local KEY_BLACKLIST = bit.bor( IN_ATTACK, IN_ATTACK2 ) function GM:StartCommand(client, command) if (!client:CanShootWeapon()) then command:RemoveKey(KEY_BLACKLIST) end end end function GM:CharacterVarChanged(char, varName, oldVar, newVar) if (ix.char.varHooks[varName]) then for _, v in pairs(ix.char.varHooks[varName]) do v(char, oldVar, newVar) end end end function GM:CanPlayerThrowPunch(client) if (!client:IsWepRaised()) then return false end return true end function GM:OnCharacterCreated(client, character) local faction = ix.faction.Get(character:GetFaction()) if (faction and faction.OnCharacterCreated) then faction:OnCharacterCreated(client, character) end end function GM:GetDefaultCharacterName(client, faction) local info = ix.faction.indices[faction] if (info and info.GetDefaultName) then return info:GetDefaultName(client) end end function GM:CanPlayerUseCharacter(client, character) local banned = character:GetData("banned") if (banned) then if (!isnumber(banned)) then return false, "@charBanned" else if (banned > os.time()) then return false, "@charBannedTemp" end end end local bHasWhitelist = client:HasWhitelist(character:GetFaction()) if (!bHasWhitelist) then return false, "@noWhitelist" end end function GM:CanProperty(client, property, entity) if (client:IsAdmin()) then return true end if (CLIENT and (property == "remover" or property == "collision")) then return true end return false end function GM:PhysgunPickup(client, entity) local bPickup = self.BaseClass:PhysgunPickup(client, entity) if (!bPickup and entity:IsPlayer() and (client:IsSuperAdmin() or client:IsAdmin() and !entity:IsSuperAdmin())) then bPickup = true end if (bPickup) then if (entity:IsPlayer()) then entity:SetMoveType(MOVETYPE_NONE) elseif (!entity.ixCollisionGroup) then entity.ixCollisionGroup = entity:GetCollisionGroup() entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) end end return bPickup end function GM:PhysgunDrop(client, entity) if (entity:IsPlayer()) then entity:SetMoveType(MOVETYPE_WALK) elseif (entity.ixCollisionGroup) then entity:SetCollisionGroup(entity.ixCollisionGroup) entity.ixCollisionGroup = nil end end do local TOOL_DANGEROUS = {} TOOL_DANGEROUS["dynamite"] = true TOOL_DANGEROUS["duplicator"] = true function GM:CanTool(client, trace, tool) if (client:IsAdmin()) then return true end if (TOOL_DANGEROUS[tool]) then return false end return self.BaseClass:CanTool(client, trace, tool) end end function GM:Move(client, moveData) local char = client:GetCharacter() if (char) then if (client:GetNetVar("actEnterAngle")) then moveData:SetForwardSpeed(0) moveData:SetSideSpeed(0) moveData:SetVelocity(vector_origin) end if (client:GetMoveType() == MOVETYPE_WALK and moveData:KeyDown(IN_WALK)) then local mf, ms = 0, 0 local speed = client:GetWalkSpeed() local ratio = ix.config.Get("walkRatio") if (moveData:KeyDown(IN_FORWARD)) then mf = ratio elseif (moveData:KeyDown(IN_BACK)) then mf = -ratio end if (moveData:KeyDown(IN_MOVELEFT)) then ms = -ratio elseif (moveData:KeyDown(IN_MOVERIGHT)) then ms = ratio end moveData:SetForwardSpeed(mf * speed) moveData:SetSideSpeed(ms * speed) end end end function GM:CanTransferItem(itemObject, curInv, inventory) if (SERVER) then local client = itemObject.GetOwner and itemObject:GetOwner() or nil if (IsValid(client) and curInv.GetReceivers) then local bAuthorized = false for _, v in ipairs(curInv:GetReceivers()) do if (client == v) then bAuthorized = true break end end if (!bAuthorized) then return false end end end -- we can transfer anything that isn't a bag if (!itemObject or !itemObject.isBag) then return end -- don't allow bags to be put inside bags if (inventory.id != 0 and curInv.id != inventory.id) then if (inventory.vars and inventory.vars.isBag) then local owner = itemObject:GetOwner() if (IsValid(owner)) then owner:NotifyLocalized("nestedBags") end return false end elseif (inventory.id != 0 and curInv.id == inventory.id) then -- we are simply moving items around if we're transferring to the same inventory return end inventory = ix.item.inventories[itemObject:GetData("id")] -- don't allow transferring items that are in use if (inventory) then for k, _ in inventory:Iter() do if (k:GetData("equip") == true) then local owner = itemObject:GetOwner() if (owner and IsValid(owner)) then owner:NotifyLocalized("equippedBag") end return false end end end end function GM:CanPlayerEquipItem(client, item) return item.invID == client:GetCharacter():GetInventory():GetID() end function GM:CanPlayerUnequipItem(client, item) return item.invID == client:GetCharacter():GetInventory():GetID() end function GM:OnItemTransferred(item, curInv, inventory) local bagInventory = item.GetInventory and item:GetInventory() if (!bagInventory) then return end -- we need to retain the receiver if the owner changed while viewing as storage if (inventory.storageInfo and isfunction(curInv.GetOwner)) then bagInventory:AddReceiver(curInv:GetOwner()) end end function GM:ShowHelp() end function GM:PreGamemodeLoaded() hook.Remove("PostDrawEffects", "RenderWidgets") hook.Remove("PlayerTick", "TickWidgets") hook.Remove("RenderScene", "RenderStereoscopy") end function GM:PostGamemodeLoaded() baseclass.Set("ix_character", ix.meta.character) baseclass.Set("ix_inventory", ix.meta.inventory) baseclass.Set("ix_item", ix.meta.item) end if (SERVER) then util.AddNetworkString("PlayerVehicle") function GM:PlayerEnteredVehicle(client, vehicle, role) UpdateAnimationTable(client) net.Start("PlayerVehicle") net.WriteEntity(client) net.WriteEntity(vehicle) net.WriteBool(true) net.Broadcast() end function GM:PlayerLeaveVehicle(client, vehicle) UpdateAnimationTable(client) net.Start("PlayerVehicle") net.WriteEntity(client) net.WriteEntity(vehicle) net.WriteBool(false) net.Broadcast() end else net.Receive("PlayerVehicle", function(length) local client = net.ReadEntity() local vehicle = net.ReadEntity() local bEntered = net.ReadBool() UpdateAnimationTable(client, bEntered and vehicle or false) end) end ================================================ FILE: gamemode/core/hooks/sv_hooks.lua ================================================ util.AddNetworkString("ixPlayerDeath") function GM:PlayerInitialSpawn(client) client.ixJoinTime = RealTime() if (client:IsBot()) then local botID = os.time() + client:EntIndex() local index = math.random(1, table.Count(ix.faction.indices)) local faction = ix.faction.indices[index] local models = faction:GetModels( client ) local model = models[ math.random( #models ) ] if ( istable( model ) ) then model = model[ 1 ] end if ( !isstring( model ) ) then model = "models/gman.mdl" end local character = ix.char.New({ name = client:Name(), faction = faction and faction.uniqueID or "unknown", model = model, }, botID, client, client:SteamID64()) character.isBot = true local inventory = ix.inventory.Create(ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight"), botID) inventory:SetOwner(botID) inventory.noSave = true character.vars.inv = {inventory} ix.char.loaded[botID] = character character:Setup() client:Spawn() ix.chat.Send(nil, "connect", client:SteamName()) return end ix.config.Send(client) ix.date.Send(client) client:LoadData(function(data) if (!IsValid(client)) then return end -- Don't use the character cache if they've connected to another server using the same database local address = ix.util.GetAddress() local bNoCache = client:GetData("lastIP", address) != address client:SetData("lastIP", address) net.Start("ixDataSync") net.WriteTable(data or {}) net.WriteUInt(client.ixPlayTime or 0, 32) net.Send(client) ix.char.Restore(client, function(charList) if (!IsValid(client)) then return end MsgN("Loaded (" .. table.concat(charList, ", ") .. ") for " .. client:Name()) for _, v in ipairs(charList) do ix.char.loaded[v]:Sync(client) end client.ixCharList = charList net.Start("ixCharacterMenu") net.WriteUInt(#charList, 6) for _, v in ipairs(charList) do net.WriteUInt(v, 32) end net.Send(client) client.ixLoaded = true client:SetData("intro", true) for _, v in player.Iterator() do if (v:GetCharacter()) then v:GetCharacter():Sync(client) end end end, bNoCache) ix.chat.Send(nil, "connect", client:SteamName()) end) client:SetNoDraw(true) client:SetNotSolid(true) client:Lock() client:SyncVars() timer.Simple(1, function() if (!IsValid(client)) then return end client:KillSilent() client:StripAmmo() end) end function GM:PlayerUse(client, entity) if (client:IsRestricted() or (isfunction(entity.GetEntityMenu) and entity:GetClass() != "ix_item")) then return false end return true end function GM:KeyPress(client, key) if (key == IN_RELOAD) then timer.Create("ixToggleRaise"..client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function() if (IsValid(client)) then client:ToggleWepRaised() end end) elseif (key == IN_USE) then local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local entity = util.TraceLine(data).Entity if (IsValid(entity) and hook.Run("PlayerUse", client, entity)) then if (entity:IsDoor()) then local result = hook.Run("CanPlayerUseDoor", client, entity) if (result != false) then hook.Run("PlayerUseDoor", client, entity) end end end end end function GM:KeyRelease(client, key) if (key == IN_RELOAD) then timer.Remove("ixToggleRaise" .. client:SteamID()) elseif (key == IN_USE) then timer.Remove("ixCharacterInteraction" .. client:SteamID()) end end function GM:CanPlayerInteractEntity(client, entity, option, data) return entity:GetPos():DistToSqr(client:GetPos()) <= 96 ^ 2 end function GM:CanPlayerInteractItem(client, action, item, data) if (client:IsRestricted()) then return false end if (IsValid(client.ixRagdoll)) then client:NotifyLocalized("notNow") return false end if (action == "drop" and hook.Run("CanPlayerDropItem", client, item) == false) then return false end if (action == "take" and hook.Run("CanPlayerTakeItem", client, item) == false) then return false end if (action == "combine") then local other = data[1] if (hook.Run("CanPlayerCombineItem", client, item, other) == false) then return false end local combineItem = ix.item.instances[other] if (combineItem and combineItem.invID != 0) then local combineInv = ix.item.inventories[combineItem.invID] if (!combineInv:OnCheckAccess(client)) then return false end else return false end end if (isentity(item) and item.ixSteamID and item.ixCharID and item.ixSteamID == client:SteamID() and item.ixCharID != client:GetCharacter():GetID() and !item:GetItemTable().bAllowMultiCharacterInteraction) then client:NotifyLocalized("itemOwned") return false end return client:Alive() end function GM:CanPlayerDropItem(client, item) end function GM:CanPlayerTakeItem(client, item) end function GM:CanPlayerCombineItem(client, item, other) end function GM:PlayerShouldTakeDamage(client, attacker) return client:GetCharacter() != nil end function GM:GetFallDamage(client, speed) return (speed - 580) * (100 / 444) end function GM:EntityTakeDamage(entity, dmgInfo) local inflictor = dmgInfo:GetInflictor() if (IsValid(inflictor) and inflictor:GetClass() == "ix_item") then dmgInfo:SetDamage(0) return end if (IsValid(entity.ixPlayer)) then if (IsValid(entity.ixHeldOwner)) then dmgInfo:SetDamage(0) return end if (dmgInfo:IsDamageType(DMG_CRUSH)) then if ((entity.ixFallGrace or 0) < CurTime()) then if (dmgInfo:GetDamage() <= 10) then dmgInfo:SetDamage(0) end entity.ixFallGrace = CurTime() + 0.5 else return end end entity.ixPlayer:TakeDamageInfo(dmgInfo) end end function GM:PrePlayerLoadedCharacter(client, character, lastChar) -- Reset all bodygroups client:ResetBodygroups() -- Remove all skins client:SetSkin(0) end function GM:PlayerLoadedCharacter(client, character, lastChar) local query = mysql:Update("ix_characters") query:Where("id", character:GetID()) query:Update("last_join_time", math.floor(os.time())) query:Execute() if (lastChar) then local charEnts = lastChar:GetVar("charEnts") or {} for _, v in ipairs(charEnts) do if (v and IsValid(v)) then v:Remove() end end lastChar:SetVar("charEnts", nil) end if (character) then for _, v in pairs(ix.class.list) do if (v.faction == client:Team() and v.isDefault) then character:SetClass(v.index) break end end end if (IsValid(client.ixRagdoll)) then client.ixRagdoll.ixNoReset = true client.ixRagdoll.ixIgnoreDelete = true client.ixRagdoll:Remove() end local faction = ix.faction.indices[character:GetFaction()] local uniqueID = "ixSalary" .. client:SteamID64() if (faction and faction.pay and faction.pay > 0) then timer.Create(uniqueID, faction.payTime or 300, 0, function() if (IsValid(client)) then if (hook.Run("CanPlayerEarnSalary", client, faction) != false) then local pay = hook.Run("GetSalaryAmount", client, faction) or faction.pay character:GiveMoney(pay) client:NotifyLocalized("salary", ix.currency.Get(pay)) end else timer.Remove(uniqueID) end end) elseif (timer.Exists(uniqueID)) then timer.Remove(uniqueID) end hook.Run("PlayerLoadout", client) end function GM:CharacterLoaded(character) local client = character:GetPlayer() if (IsValid(client)) then local uniqueID = "ixSaveChar"..client:SteamID() timer.Create(uniqueID, ix.config.Get("saveInterval"), 0, function() if (IsValid(client) and client:GetCharacter()) then client:GetCharacter():Save() else timer.Remove(uniqueID) end end) end end function GM:PlayerSay(client, text) local chatType, message, anonymous = ix.chat.Parse(client, text, true) if (chatType == "ic") then if (ix.command.Parse(client, message)) then return "" end end text = ix.chat.Send(client, chatType, message, anonymous) if (isstring(text) and chatType != "ic") then ix.log.Add(client, "chat", chatType and chatType:utf8upper() or "??", text) end hook.Run("PostPlayerSay", client, chatType, message, anonymous) return "" end function GM:CanAutoFormatMessage(client, chatType, message) return chatType == "ic" or chatType == "w" or chatType == "y" end function GM:PlayerSpawn(client) client:SetNoDraw(false) client:UnLock() client:SetNotSolid(false) client:SetMoveType(MOVETYPE_WALK) client:SetRagdolled(false) client:SetAction() client:SetDSP(1) hook.Run("PlayerLoadout", client) end -- Shortcuts for (super)admin only things. local function IsAdmin(_, client) return client:IsAdmin() end -- Set the gamemode hooks to the appropriate shortcuts. GM.PlayerGiveSWEP = IsAdmin GM.PlayerSpawnEffect = IsAdmin GM.PlayerSpawnSENT = IsAdmin function GM:PlayerSpawnNPC(client, npcType, weapon) return client:IsAdmin() or client:GetCharacter():HasFlags("n") end function GM:PlayerSpawnSWEP(client, weapon, info) return client:IsAdmin() end function GM:PlayerSpawnProp(client) if (client:GetCharacter() and client:GetCharacter():HasFlags("e")) then return true end return false end function GM:PlayerSpawnRagdoll(client) if (client:GetCharacter() and client:GetCharacter():HasFlags("r")) then return true end return false end function GM:PlayerSpawnVehicle(client, model, name, data) if (client:GetCharacter()) then if (data.Category == "Chairs") then return client:GetCharacter():HasFlags("c") else return client:GetCharacter():HasFlags("C") end end return false end function GM:PlayerSpawnedEffect(client, model, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end function GM:PlayerSpawnedNPC(client, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end function GM:PlayerSpawnedProp(client, model, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end function GM:PlayerSpawnedRagdoll(client, model, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end function GM:PlayerSpawnedSENT(client, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end function GM:PlayerSpawnedSWEP(client, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end function GM:PlayerSpawnedVehicle(client, entity) entity:SetNetVar("owner", client:GetCharacter():GetID()) end ix.allowedHoldableClasses = { ["ix_item"] = true, ["ix_money"] = true, ["ix_shipment"] = true, ["prop_physics"] = true, ["prop_physics_override"] = true, ["prop_physics_multiplayer"] = true, ["prop_ragdoll"] = true } function GM:CanPlayerHoldObject(client, entity) if (ix.allowedHoldableClasses[entity:GetClass()]) then return true end end local voiceDistance = 360000 local function CalcPlayerCanHearPlayersVoice(listener) if (!IsValid(listener)) then return end listener.ixVoiceHear = listener.ixVoiceHear or {} local eyePos = listener:EyePos() for _, speaker in player.Iterator() do local speakerEyePos = speaker:EyePos() listener.ixVoiceHear[speaker] = eyePos:DistToSqr(speakerEyePos) < voiceDistance end end function GM:InitializedConfig() ix.date.Initialize() voiceDistance = ix.config.Get("voiceDistance") voiceDistance = voiceDistance * voiceDistance end function GM:VoiceToggled(bAllowVoice) for _, v in player.Iterator() do local uniqueID = v:SteamID64() .. "ixCanHearPlayersVoice" if (bAllowVoice) then timer.Create(uniqueID, 0.5, 0, function() CalcPlayerCanHearPlayersVoice(v) end) else timer.Remove(uniqueID) v.ixVoiceHear = nil end end end function GM:VoiceDistanceChanged(distance) voiceDistance = distance * distance end -- Called when weapons should be given to a player. function GM:PlayerLoadout(client) if (client.ixSkipLoadout) then client.ixSkipLoadout = nil return end client:SetWeaponColor(Vector(client:GetInfo("cl_weaponcolor"))) client:StripWeapons() client:StripAmmo() client:SetLocalVar("blur", nil) local character = client:GetCharacter() -- Check if they have loaded a character. if (character) then client:SetupHands() -- Set their player model to the character's model. client:SetModel(character:GetModel()) client:Give("ix_hands") client:SetWalkSpeed(ix.config.Get("walkSpeed")) client:SetRunSpeed(ix.config.Get("runSpeed")) client:SetHealth(character:GetData("health", client:GetMaxHealth())) local faction = ix.faction.indices[client:Team()] if (faction) then -- If their faction wants to do something when the player spawns, let it. if (faction.OnSpawn) then faction:OnSpawn(client) end -- @todo add docs for player:Give() failing if player already has weapon - which means if a player is given a weapon -- here due to the faction weapons table, the weapon's :Give call in the weapon base will fail since the player -- will already have it by then. This will cause issues for weapons that have pac data since the parts are applied -- only if the weapon returned by :Give() is valid -- If the faction has default weapons, give them to the player. if (faction.weapons) then for _, v in ipairs(faction.weapons) do client:Give(v) end end end -- Ditto, but for classes. local class = ix.class.list[client:GetCharacter():GetClass()] if (class) then if (class.OnSpawn) then class:OnSpawn(client) end if (class.weapons) then for _, v in ipairs(class.weapons) do client:Give(v) end end end -- Apply any flags as needed. ix.flag.OnSpawn(client) ix.attributes.Setup(client) hook.Run("PostPlayerLoadout", client) client:SelectWeapon("ix_hands") else client:SetNoDraw(true) client:Lock() client:SetNotSolid(true) end end function GM:PostPlayerLoadout(client) -- Reload All Attrib Boosts local character = client:GetCharacter() if (character:GetInventory()) then for k, _ in character:GetInventory():Iter() do k:Call("OnLoadout", client) if (k:GetData("equip") and k.attribBoosts) then for attribKey, attribValue in pairs(k.attribBoosts) do character:AddBoost(k.uniqueID, attribKey, attribValue) end end end end if (ix.config.Get("allowVoice")) then timer.Create(client:SteamID64() .. "ixCanHearPlayersVoice", 0.5, 0, function() CalcPlayerCanHearPlayersVoice(client) end) end end local deathSounds = { Sound("vo/npc/male01/pain07.wav"), Sound("vo/npc/male01/pain08.wav"), Sound("vo/npc/male01/pain09.wav") } function GM:DoPlayerDeath(client, attacker, damageinfo) client:AddDeaths(1) if (hook.Run("ShouldSpawnClientRagdoll", client) != false) then client:CreateRagdoll() end if (IsValid(attacker) and attacker:IsPlayer()) then if (client == attacker) then attacker:AddFrags(-1) else attacker:AddFrags(1) end end net.Start("ixPlayerDeath") net.Send(client) client:SetAction("@respawning", ix.config.Get("spawnTime", 5)) client:SetDSP(31) end function GM:PlayerDeath(client, inflictor, attacker) local character = client:GetCharacter() if (character) then if (IsValid(client.ixRagdoll)) then client.ixRagdoll.ixIgnoreDelete = true client:SetLocalVar("blur", nil) if (hook.Run("ShouldRemoveRagdollOnDeath", client) != false) then client.ixRagdoll:Remove() end end client:SetNetVar("deathStartTime", CurTime()) client:SetNetVar("deathTime", CurTime() + ix.config.Get("spawnTime", 5)) character:SetData("health", nil) local deathSound = hook.Run("GetPlayerDeathSound", client) if (deathSound != false) then deathSound = deathSound or deathSounds[math.random(1, #deathSounds)] if (client:IsFemale() and !deathSound:find("female")) then deathSound = deathSound:gsub("male", "female") end client:EmitSound(deathSound) end local weapon = attacker:IsPlayer() and attacker:GetActiveWeapon() ix.log.Add(client, "playerDeath", attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass(), IsValid(weapon) and weapon:GetClass()) end end local painSounds = { Sound("vo/npc/male01/pain01.wav"), Sound("vo/npc/male01/pain02.wav"), Sound("vo/npc/male01/pain03.wav"), Sound("vo/npc/male01/pain04.wav"), Sound("vo/npc/male01/pain05.wav"), Sound("vo/npc/male01/pain06.wav") } local drownSounds = { Sound("player/pl_drown1.wav"), Sound("player/pl_drown2.wav"), Sound("player/pl_drown3.wav"), } function GM:GetPlayerPainSound(client) if (client:WaterLevel() >= 3) then return drownSounds[math.random(1, #drownSounds)] end end function GM:PlayerHurt(client, attacker, health, damage) if ((client.ixNextPain or 0) < CurTime() and health > 0) then local painSound = hook.Run("GetPlayerPainSound", client) or painSounds[math.random(1, #painSounds)] if (client:IsFemale() and !painSound:find("female")) then painSound = painSound:gsub("male", "female") end client:EmitSound(painSound) client.ixNextPain = CurTime() + 0.33 end ix.log.Add(client, "playerHurt", damage, attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass()) end function GM:PlayerDeathThink(client) if (client:GetCharacter()) then local deathTime = client:GetNetVar("deathTime") if (deathTime and deathTime <= CurTime()) then client:Spawn() end end return false end function GM:PlayerDisconnected(client) client:SaveData() local character = client:GetCharacter() if (character) then local charEnts = character:GetVar("charEnts") or {} for _, v in ipairs(charEnts) do if (v and IsValid(v)) then v:Remove() end end hook.Run("OnCharacterDisconnect", client, character) character:Save() ix.chat.Send(nil, "disconnect", client:SteamName()) end if (IsValid(client.ixRagdoll)) then client.ixRagdoll:Remove() end client:ClearNetVars() if (!client.ixVoiceHear) then return end for _, v in player.Iterator() do if (!v.ixVoiceHear) then continue end v.ixVoiceHear[client] = nil end timer.Remove(client:SteamID64() .. "ixCanHearPlayersVoice") end function GM:InitPostEntity() local doors = ents.FindByClass("prop_door_rotating") for _, v in ipairs(doors) do local parent = v:GetOwner() if (IsValid(parent)) then v.ixPartner = parent parent.ixPartner = v else for _, v2 in ipairs(doors) do if (v2:GetOwner() == v) then v2.ixPartner = v v.ixPartner = v2 break end end end end timer.Simple(2, function() ix.entityDataLoaded = true end) end function GM:SaveData() ix.date.Save() end function GM:ShutDown() ix.shuttingDown = true ix.config.Save() hook.Run("SaveData") for _, v in player.Iterator() do v:SaveData() if (v:GetCharacter()) then v:GetCharacter():Save() end end end function GM:GetGameDescription() return "IX: "..(Schema and Schema.name or "Unknown") end function GM:OnPlayerUseBusiness(client, item) -- You can manipulate purchased items with this hook. -- does not requires any kind of return. -- ex) item:SetData("businessItem", true) -- then every purchased item will be marked as Business Item. end function GM:PlayerDeathSound() return true end function GM:InitializedSchema() game.ConsoleCommand("sbox_persist ix_"..Schema.folder.."\n") end function GM:PlayerCanHearPlayersVoice(listener, speaker) if (!speaker:Alive()) then return false end local bCanHear = listener.ixVoiceHear and listener.ixVoiceHear[speaker] return bCanHear, true end function GM:PlayerCanPickupWeapon(client, weapon) local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local trace = util.TraceLine(data) if (trace.Entity == weapon and client:KeyDown(IN_USE)) then return true end return client.ixWeaponGive end function GM:OnPhysgunFreeze(weapon, physObj, entity, client) -- Validate the physObj, to prevent errors on entities who have no physics object if (!IsValid(physObj)) then return false end -- Object is already frozen (!?) if (!physObj:IsMoveable()) then return false end if (entity:GetUnFreezable()) then return false end physObj:EnableMotion(false) -- With the jeep we need to pause all of its physics objects -- to stop it spazzing out and killing the server. if (entity:GetClass() == "prop_vehicle_jeep") then local objects = entity:GetPhysicsObjectCount() for i = 0, objects - 1 do entity:GetPhysicsObjectNum(i):EnableMotion(false) end end -- Add it to the player's frozen props client:AddFrozenPhysicsObject(entity, physObj) client:SendHint("PhysgunUnfreeze", 0.3) client:SuppressHint("PhysgunFreeze") return true end function GM:CanPlayerSuicide(client) return false end function GM:AllowPlayerPickup(client, entity) return false end function GM:PreCleanupMap() hook.Run("SaveData") hook.Run("PersistenceSave") end function GM:PostCleanupMap() ix.plugin.RunLoadData() end function GM:CharacterPreSave(character) local client = character:GetPlayer() for k, _ in character:GetInventory():Iter() do if (k.OnSave) then k:Call("OnSave", client) end end character:SetData("health", client:Alive() and client:Health() or nil) end timer.Create("ixLifeGuard", 1, 0, function() for _, v in player.Iterator() do if (v:GetCharacter() and v:Alive() and hook.Run("ShouldPlayerDrowned", v) != false) then if (v:WaterLevel() >= 3) then if (!v.drowningTime) then v.drowningTime = CurTime() + 30 v.nextDrowning = CurTime() v.drownDamage = v.drownDamage or 0 end if (v.drowningTime < CurTime()) then if (v.nextDrowning < CurTime()) then v:ScreenFade(1, Color(0, 0, 255, 100), 1, 0) v:TakeDamage(10) v.drownDamage = v.drownDamage + 10 v.nextDrowning = CurTime() + 1 end end else if (v.drowningTime) then v.drowningTime = nil v.nextDrowning = nil v.nextRecover = CurTime() + 2 end if (v.nextRecover and v.nextRecover < CurTime() and v.drownDamage > 0) then v.drownDamage = v.drownDamage - 10 v:SetHealth(math.Clamp(v:Health() + 10, 0, v:GetMaxHealth())) v.nextRecover = CurTime() + 1 end end end end end) net.Receive("ixStringRequest", function(length, client) local time = net.ReadUInt(32) local text = net.ReadString() if (client.ixStrReqs and client.ixStrReqs[time]) then client.ixStrReqs[time](text) client.ixStrReqs[time] = nil end end) function GM:GetPreferredCarryAngles(entity) if (entity:GetClass() == "ix_item") then local itemTable = entity:GetItemTable() if (itemTable) then local preferedAngle = itemTable.preferedAngle if (preferedAngle) then -- I don't want to return something return preferedAngle end end end end function GM:PluginShouldLoad(uniqueID) return !ix.plugin.unloaded[uniqueID] end function GM:DatabaseConnected() -- Create the SQL tables if they do not exist. ix.db.LoadTables() ix.log.LoadTables() MsgC(Color(0, 255, 0), "Database Type: " .. ix.db.config.adapter .. ".\n") timer.Create("ixDatabaseThink", 0.5, 0, function() mysql:Think() end) ix.plugin.RunLoadData() end ================================================ FILE: gamemode/core/libs/cl_bar.lua ================================================ ix.bar = ix.bar or {} ix.bar.list = {} ix.bar.delta = ix.bar.delta or {} ix.bar.actionText = "" ix.bar.actionStart = 0 ix.bar.actionEnd = 0 ix.bar.totalHeight = 0 -- luacheck: globals BAR_HEIGHT BAR_HEIGHT = 10 function ix.bar.Get(identifier) for _, v in ipairs(ix.bar.list) do if (v.identifier == identifier) then return v end end end function ix.bar.Remove(identifier) local bar = ix.bar.Get(identifier) if (bar) then table.remove(ix.bar.list, bar.index) if (IsValid(ix.gui.bars)) then ix.gui.bars:RemoveBar(bar.panel) end end end function ix.bar.Add(getValue, color, priority, identifier) if (identifier) then ix.bar.Remove(identifier) end local index = #ix.bar.list + 1 color = color or Color(math.random(150, 255), math.random(150, 255), math.random(150, 255)) priority = priority or index ix.bar.list[index] = { index = index, color = color, priority = priority, GetValue = getValue, identifier = identifier, panel = IsValid(ix.gui.bars) and ix.gui.bars:AddBar(index, color, priority) } return priority end local gradientD = ix.util.GetMaterial("vgui/gradient-d") local TEXT_COLOR = Color(240, 240, 240) local SHADOW_COLOR = Color(20, 20, 20) function ix.bar.DrawAction() local start, finish = ix.bar.actionStart, ix.bar.actionEnd local curTime = CurTime() local scrW, scrH = ScrW(), ScrH() if (finish > curTime) then local fraction = 1 - math.TimeFraction(start, finish, curTime) local alpha = fraction * 255 if (alpha > 0) then local w, h = scrW * 0.35, 28 local x, y = (scrW * 0.5) - (w * 0.5), (scrH * 0.725) - (h * 0.5) ix.util.DrawBlurAt(x, y, w, h) surface.SetDrawColor(35, 35, 35, 100) surface.DrawRect(x, y, w, h) surface.SetDrawColor(0, 0, 0, 120) surface.DrawOutlinedRect(x, y, w, h) surface.SetDrawColor(ix.config.Get("color")) surface.DrawRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8) surface.SetDrawColor(200, 200, 200, 20) surface.SetMaterial(gradientD) surface.DrawTexturedRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8) draw.SimpleText(ix.bar.actionText, "ixMediumFont", x + 2, y - 22, SHADOW_COLOR) draw.SimpleText(ix.bar.actionText, "ixMediumFont", x, y - 24, TEXT_COLOR) end end end do ix.bar.Add(function() return math.max(LocalPlayer():Health() / LocalPlayer():GetMaxHealth(), 0) end, Color(200, 50, 40), nil, "health") ix.bar.Add(function() return math.min(LocalPlayer():Armor() / 100, 1) end, Color(30, 70, 180), nil, "armor") end net.Receive("ixActionBar", function() local start, finish = net.ReadFloat(), net.ReadFloat() local text = net.ReadString() if (text:sub(1, 1) == "@") then text = L2(text:sub(2)) or text end ix.bar.actionStart = start ix.bar.actionEnd = finish ix.bar.actionText = text:utf8upper() end) net.Receive("ixActionBarReset", function() ix.bar.actionStart = 0 ix.bar.actionEnd = 0 ix.bar.actionText = "" end) ================================================ FILE: gamemode/core/libs/cl_hud.lua ================================================ ix.hud = {} function ix.hud.DrawItemPickup() local pickupTime = ix.config.Get("itemPickupTime", 0.5) if (pickupTime == 0) then return end local client = LocalPlayer() local entity = client.ixInteractionTarget local startTime = client.ixInteractionStartTime if (IsValid(entity) and startTime) then local sysTime = SysTime() local endTime = startTime + pickupTime if (sysTime >= endTime or client:GetEyeTrace().Entity != entity) then client.ixInteractionTarget = nil client.ixInteractionStartTime = nil return end local fraction = math.min((endTime - sysTime) / pickupTime, 1) local x, y = ScrW() / 2, ScrH() / 2 local radius, thickness = 32, 6 local startAngle = 90 local endAngle = startAngle + (1 - fraction) * 360 local color = ColorAlpha(color_white, fraction * 255) ix.util.DrawArc(x, y, radius, thickness, startAngle, endAngle, 2, color) end end function ix.hud.PopulateItemTooltip(tooltip, item) local name = tooltip:AddRow("name") name:SetImportant() name:SetText(item.GetName and item:GetName() or L(item.name)) name:SetMaxWidth(math.max(name:GetMaxWidth(), ScrW() * 0.5)) name:SizeToContents() local description = tooltip:AddRow("description") description:SetText(item:GetDescription() or "") description:SizeToContents() if (item.PopulateTooltip) then item:PopulateTooltip(tooltip) end hook.Run("PopulateItemTooltip", tooltip, item) end function ix.hud.PopulatePlayerTooltip(tooltip, client) local name = tooltip:AddRow("name") name:SetImportant() name:SetText(client:SteamName()) name:SetBackgroundColor(team.GetColor(client:Team())) name:SizeToContents() local nameHeight = name:GetTall() name:SetTextInset(nameHeight + 4, 0) name:SetWide(name:GetWide() + nameHeight + 4) local avatar = name:Add("AvatarImage") avatar:Dock(LEFT) avatar:SetPlayer(client, nameHeight) avatar:SetSize(name:GetTall(), name:GetTall()) local currentPing = client:Ping() local ping = tooltip:AddRow("ping") ping:SetText(L("ping", currentPing)) ping.Paint = function(_, width, height) surface.SetDrawColor(ColorAlpha(derma.GetColor( currentPing < 110 and "Success" or (currentPing < 165 and "Warning" or "Error") , tooltip), 22)) surface.DrawRect(0, 0, width, height) end ping:SizeToContents() hook.Run("PopulatePlayerTooltip", client, tooltip) end function ix.hud.DrawAll() ix.hud.DrawItemPickup() end ================================================ FILE: gamemode/core/libs/cl_markup.lua ================================================ -- luacheck: ignore ix.markup = ix.markup or {} -- Temporary information used when building text frames. local colour_stack = { {r=255,g=255,b=255,a=255} } local font_stack = { "DermaDefault" } local curtag = nil local blocks = {} local colourmap = { -- it's all black and white ["black"] = { r=0, g=0, b=0, a=255 }, ["white"] = { r=255, g=255, b=255, a=255 }, -- it's greys ["dkgrey"] = { r=64, g=64, b=64, a=255 }, ["grey"] = { r=128, g=128, b=128, a=255 }, ["ltgrey"] = { r=192, g=192, b=192, a=255 }, -- account for speeling mistakes ["dkgray"] = { r=64, g=64, b=64, a=255 }, ["gray"] = { r=128, g=128, b=128, a=255 }, ["ltgray"] = { r=192, g=192, b=192, a=255 }, -- normal colours ["red"] = { r=255, g=0, b=0, a=255 }, ["green"] = { r=0, g=255, b=0, a=255 }, ["blue"] = { r=0, g=0, b=255, a=255 }, ["yellow"] = { r=255, g=255, b=0, a=255 }, ["purple"] = { r=255, g=0, b=255, a=255 }, ["cyan"] = { r=0, g=255, b=255, a=255 }, ["turq"] = { r=0, g=255, b=255, a=255 }, -- dark variations ["dkred"] = { r=128, g=0, b=0, a=255 }, ["dkgreen"] = { r=0, g=128, b=0, a=255 }, ["dkblue"] = { r=0, g=0, b=128, a=255 }, ["dkyellow"] = { r=128, g=128, b=0, a=255 }, ["dkpurple"] = { r=128, g=0, b=128, a=255 }, ["dkcyan"] = { r=0, g=128, b=128, a=255 }, ["dkturq"] = { r=0, g=128, b=128, a=255 }, -- light variations ["ltred"] = { r=255, g=128, b=128, a=255 }, ["ltgreen"] = { r=128, g=255, b=128, a=255 }, ["ltblue"] = { r=128, g=128, b=255, a=255 }, ["ltyellow"] = { r=255, g=255, b=128, a=255 }, ["ltpurple"] = { r=255, g=128, b=255, a=255 }, ["ltcyan"] = { r=128, g=255, b=255, a=255 }, ["ltturq"] = { r=128, g=255, b=255, a=255 }, } --[[ Name: colourMatch(c) Desc: Match a colour name to an rgb value. Usage: ** INTERNAL ** Do not use! ]] local function colourMatch(c) c = string.lower(c) return colourmap[c] end --[[ Name: ExtractParams(p1,p2,p3) Desc: This function is used to extract the tag information. Usage: ** INTERNAL ** Do not use! ]] local function ExtractParams(p1,p2,p3) if (string.utf8sub(p1, 1, 1) == "/") then local tag = string.utf8sub(p1, 2) if (tag == "color" or tag == "colour") then table.remove(colour_stack) elseif (tag == "font" or tag == "face") then table.remove(font_stack) end else if (p1 == "color" or p1 == "colour") then local rgba = colourMatch(p2) if (rgba == nil) then rgba = {} local x = { "r", "g", "b", "a" } n = 1 for k, v in string.gmatch(p2, "(%d+),?") do rgba[ x[n] ] = k n = n + 1 end end table.insert(colour_stack, rgba) elseif (p1 == "font" or p1 == "face") then table.insert(font_stack, tostring(p2)) elseif (p1 == "img" and p2) then local exploded = string.Explode(",", p2) local material = exploded[1] or p2 local p3 = exploded[2] local found = file.Find("materials/"..material..".*", "GAME") if (found[1] and found[1]:find("%.png")) then material = material..".png" end local texture = Material(material) local sizeData = string.Explode("x", p3 or "16x16") w = tonumber(sizeData[1]) or 16 h = tonumber(sizeData[2]) or 16 if (texture) then table.insert(blocks, { texture = texture, w = w, h = h }) end end end end --[[ Name: CheckTextOrTag(p) Desc: This function places data in the "blocks" table depending of if p is a tag, or some text Usage: ** INTERNAL ** Do not use! ]] local function CheckTextOrTag(p) if (p == "") then return end if (p == nil) then return end if (string.utf8sub(p, 1, 1) == "<") then string.gsub(p, "<([/%a]*)=?([^>]*)", ExtractParams) else local text_block = {} text_block.text = p text_block.colour = colour_stack[#colour_stack] text_block.font = font_stack[#font_stack] table.insert(blocks, text_block) end end --[[ Name: ProcessMatches(p1,p2,p3) Desc: CheckTextOrTag for 3 parameters. Called by string.gsub Usage: ** INTERNAL ** Do not use! ]] local function ProcessMatches(p1,p2,p3) if (p1) then CheckTextOrTag(p1) end if (p2) then CheckTextOrTag(p2) end if (p3) then CheckTextOrTag(p3) end end local MarkupObject = {} --[[ Name: MarkupObject:Create() Desc: Called by Parse. Creates a new table, and setups the metatable. Usage: ** INTERNAL ** Do not use! ]] function MarkupObject:create() local o = {} setmetatable(o, self) self.__index = self return o end --[[ Name: MarkupObject:GetWidth() Desc: Returns the width of a markup block Usage: ml:GetWidth() ]] function MarkupObject:GetWidth() return self.totalWidth end --[[ Name: MarkupObject:GetHeight() Desc: Returns the height of a markup block Usage: ml:GetHeight() ]] function MarkupObject:GetHeight() return self.totalHeight end function MarkupObject:size() return self.totalWidth, self.totalHeight end --[[ Name: MarkupObject:Draw(xOffset, yOffset, halign, valign, alphaoverride) Desc: Draw the markup text to the screen as position xOffset, yOffset. Halign and Valign can be used to align the text. Alphaoverride can be used to override the alpha value of the text-colour. Usage: MarkupObject:Draw(100, 100) ]] function MarkupObject:draw(xOffset, yOffset, halign, valign, alphaoverride) for i = 1, #self.blocks do local blk = self.blocks[i] if (blk.texture) then local y = yOffset + blk.offset.y local x = xOffset + blk.offset.x if (halign == TEXT_ALIGN_CENTER) then x = x - (self.totalWidth * 0.5) elseif (halign == TEXT_ALIGN_RIGHT) then x = x - (self.totalWidth) end surface.SetDrawColor(blk.colour.r, blk.colour.g, blk.colour.b, alphaoverride or blk.colour.a or 255) surface.SetMaterial(blk.texture) surface.DrawTexturedRect(x, y, blk.w, blk.h) else local y = yOffset + (blk.height - blk.thisY) + blk.offset.y local x = xOffset if (halign == TEXT_ALIGN_CENTER) then x = x - (self.totalWidth / 2) elseif (halign == TEXT_ALIGN_RIGHT) then x = x - (self.totalWidth) end x = x + blk.offset.x if (self.onDrawText) then self.onDrawText(blk.text, blk.font, x, y, blk.colour, halign, valign, alphaoverride, blk) else if (valign == TEXT_ALIGN_CENTER) then y = y - (self.totalHeight / 2) elseif (valign == TEXT_ALIGN_BOTTOM) then y = y - (self.totalHeight) end local alpha = blk.colour.a if (alphaoverride) then alpha = alphaoverride end surface.SetFont( blk.font ) surface.SetTextColor( blk.colour.r, blk.colour.g, blk.colour.b, alpha ) surface.SetTextPos( x, y ) surface.DrawText( blk.text ) end end end end --[[ Name: Parse(ml, maxwidth) Desc: Parses the pseudo-html markup language, and creates a MarkupObject, which can be used to the draw the text to the screen. Valid tags are: font and colour. \n and \t are also available to move to the next line, or insert a tab character. Maxwidth can be used to make the text wrap to a specific width. Usage: markup.Parse("changed font\nchanged colour") ]] function ix.markup.Parse(ml, maxwidth) ml = utf8.force(ml) colour_stack = { {r=255,g=255,b=255,a=255} } font_stack = { "DermaDefault" } blocks = {} if (not string.find(ml, "<")) then ml = ml .. "" end string.gsub(ml, "([^<>]*)(<[^>]+.)([^<>]*)", ProcessMatches) local xOffset = 0 local yOffset = 0 local xSize = 0 local xMax = 0 local thisMaxY = 0 local new_block_list = {} local ymaxes = {} local texOffset = 0 local lineHeight = 0 for i = 1, #blocks do local block = blocks[i] if (block.text) then surface.SetFont(block.font) local thisY = 0 local curString = "" block.text = string.gsub(block.text, ">", ">") block.text = string.gsub(block.text, "<", "<") block.text = string.gsub(block.text, "&", "&") for j=1,string.utf8len(block.text) do local ch = string.utf8sub(block.text,j,j) if (ch == "\n") then if (thisY == 0) then thisY = lineHeight + texOffset; thisMaxY = lineHeight + texOffset; else lineHeight = thisY + texOffset end if (string.utf8len(curString) > 0) then local x1,y1 = surface.GetTextSize(curString) local new_block = {} new_block.text = curString new_block.font = block.font new_block.colour = block.colour new_block.thisY = thisY new_block.thisX = x1 new_block.offset = {} new_block.offset.x = xOffset new_block.offset.y = yOffset table.insert(new_block_list, new_block) if (xOffset + x1 > xMax) then xMax = xOffset + x1 end end xOffset = 0 xSize = 0 yOffset = yOffset + thisMaxY; thisY = 0 curString = "" thisMaxY = 0 elseif (ch == "\t") then if (string.utf8len(curString) > 0) then local x1,y1 = surface.GetTextSize(curString) local new_block = {} new_block.text = curString new_block.font = block.font new_block.colour = block.colour new_block.thisY = thisY new_block.thisX = x1 new_block.offset = {} new_block.offset.x = xOffset new_block.offset.y = yOffset table.insert(new_block_list, new_block) if (xOffset + x1 > xMax) then xMax = xOffset + x1 end end local xOldSize = xSize xSize = 0 curString = "" local xOldOffset = xOffset xOffset = math.ceil( (xOffset + xOldSize) / 50 ) * 50 if (xOffset == xOldOffset) then xOffset = xOffset + 50 end else local x,y = surface.GetTextSize(ch) if (x == nil) then return end if (maxwidth and maxwidth > x) then if (xOffset + xSize + x >= maxwidth) then -- need to: find the previous space in the curString -- if we can't find one, take off the last character -- and add a -. add the character to ch -- and insert as a new block, incrementing the y etc local lastSpacePos = string.utf8len(curString) for k=1,string.utf8len(curString) do local chspace = string.utf8sub(curString,k,k) if (chspace == " ") then lastSpacePos = k end end if (lastSpacePos == string.utf8len(curString)) then ch = string.utf8sub(curString,lastSpacePos,lastSpacePos) .. ch j = lastSpacePos curString = string.utf8sub(curString, 1, lastSpacePos-1) else ch = string.utf8sub(curString,lastSpacePos+1) .. ch j = lastSpacePos+1 curString = string.utf8sub(curString, 1, lastSpacePos) end local m = 1 while string.utf8sub(ch, m, m) == " " and m <= string.utf8len(ch) do m = m + 1 end ch = string.utf8sub(ch, m) local x1,y1 = surface.GetTextSize(curString) if (y1 > thisMaxY) then thisMaxY = y1; ymaxes[yOffset] = thisMaxY; lineHeight = y1; end local new_block = {} new_block.text = curString new_block.font = block.font new_block.colour = block.colour new_block.thisY = thisY new_block.thisX = x1 new_block.offset = {} new_block.offset.x = xOffset new_block.offset.y = yOffset table.insert(new_block_list, new_block) if (xOffset + x1 > xMax) then xMax = xOffset + x1 end xOffset = 0 xSize = 0 x,y = surface.GetTextSize(ch) yOffset = yOffset + thisMaxY; thisY = 0 curString = "" thisMaxY = 0 end end curString = curString .. ch thisY = y xSize = xSize + x if (y > thisMaxY) then thisMaxY = y; ymaxes[yOffset] = thisMaxY; lineHeight = y; end end end if (string.utf8len(curString) > 0) then local x1,y1 = surface.GetTextSize(curString) local new_block = {} new_block.text = curString new_block.font = block.font new_block.colour = block.colour new_block.thisY = thisY new_block.thisX = x1 new_block.offset = {} new_block.offset.x = xOffset new_block.offset.y = yOffset table.insert(new_block_list, new_block) lineHeight = thisY if (xOffset + x1 > xMax) then xMax = xOffset + x1 end xOffset = xOffset + x1 end xSize = 0 elseif (block.texture) then local newBlock = table.Copy(block) newBlock.colour = block.colour or {r = 255, g = 255, b = 255, a = 255} newBlock.thisX = block.w newBlock.thisY = block.h newBlock.offset = { x = xOffset, y = 0 } table.insert(new_block_list, newBlock) xOffset = xOffset + block.w + 1 texOffset = block.h / 2 end end local totalHeight = 0 for i = 1, #new_block_list do local block = new_block_list[i] block.height = ymaxes[block.offset.y] if (block.texture) then block.offset.y = ymaxes[0] * 0.5 - block.h * 0.5 end if (block.height and block.offset.y + block.height > totalHeight) then totalHeight = block.offset.y + block.height end end local newObject = MarkupObject:create() newObject.totalHeight = totalHeight newObject.totalWidth = xMax newObject.blocks = new_block_list return newObject end ================================================ FILE: gamemode/core/libs/cl_networking.lua ================================================ local entityMeta = FindMetaTable("Entity") local playerMeta = FindMetaTable("Player") ix.net = ix.net or {} ix.net.globals = ix.net.globals or {} net.Receive("ixGlobalVarSet", function() ix.net.globals[net.ReadString()] = net.ReadType() end) net.Receive("ixNetVarSet", function() local index = net.ReadUInt(16) ix.net[index] = ix.net[index] or {} ix.net[index][net.ReadString()] = net.ReadType() end) net.Receive("ixNetVarDelete", function() ix.net[net.ReadUInt(16)] = nil end) net.Receive("ixLocalVarSet", function() local key = net.ReadString() local var = net.ReadType() ix.net[LocalPlayer():EntIndex()] = ix.net[LocalPlayer():EntIndex()] or {} ix.net[LocalPlayer():EntIndex()][key] = var hook.Run("OnLocalVarSet", key, var) end) function GetNetVar(key, default) -- luacheck: globals GetNetVar local value = ix.net.globals[key] return value != nil and value or default end function entityMeta:GetNetVar(key, default) local index = self:EntIndex() if (ix.net[index] and ix.net[index][key] != nil) then return ix.net[index][key] end return default end playerMeta.GetLocalVar = entityMeta.GetNetVar ================================================ FILE: gamemode/core/libs/sh_animation.lua ================================================ function ix.util.InstallAnimationMethods(meta) local function TweenAnimationThink(object) for k, v in pairs(object.tweenAnimations) do if (!v.bShouldPlay) then continue end local bComplete = v:update(FrameTime()) if (v.Think) then v:Think(object) end if (bComplete) then v.bShouldPlay = nil v:ForceComplete() if (v.OnComplete) then v:OnComplete(object) end if (v.bRemoveOnComplete) then object.tweenAnimations[k] = nil end end end end function meta:GetTweenAnimation(index, bNoPlay) -- if we don't need to check if the animation is playing we can just return the animation if (bNoPlay) then return self.tweenAnimations[index] else for k, v in pairs(self.tweenAnimations or {}) do if (k == index and v.bShouldPlay) then return v end end end end function meta:IsPlayingTweenAnimation(index) for k, v in pairs(self.tweenAnimations or {}) do if (v.bShouldPlay and index == k) then return true end end return false end function meta:StopAnimations(bRemove) for k, v in pairs(self.tweenAnimations or {}) do if (v.bShouldPlay) then v:ForceComplete() if (bRemove) then self.tweenAnimations[k] = nil end end end end function meta:CreateAnimation(length, data) local animations = self.tweenAnimations or {} self.tweenAnimations = animations if (self.SetAnimationEnabled) then self:SetAnimationEnabled(true) end local index = data.index or 1 local bCancelPrevious = data.bCancelPrevious == nil and false or data.bCancelPrevious local bIgnoreConfig = SERVER or (data.bIgnoreConfig == nil and false or data.bIgnoreConfig) if (bCancelPrevious and self:IsPlayingTweenAnimation()) then for _, v in pairs(animations) do v:set(v.duration) end end local animation = ix.tween.new( ((length == 0 and 1 or length) or 1) * (bIgnoreConfig and 1 or ix.option.Get("animationScale", 1)), data.subject or self, data.target or {}, data.easing or "linear" ) animation.index = index animation.bIgnoreConfig = bIgnoreConfig animation.bAutoFire = (data.bAutoFire == nil and true or data.bAutoFire) animation.bRemoveOnComplete = (data.bRemoveOnComplete == nil and true or data.bRemoveOnComplete) animation.Think = data.Think animation.OnComplete = data.OnComplete animation.ForceComplete = function(anim) anim:set(anim.duration) end -- @todo don't use ridiculous method chaining animation.CreateAnimation = function(currentAnimation, newLength, newData) newData.bAutoFire = false newData.index = currentAnimation.index + 1 local oldOnComplete = currentAnimation.OnComplete local newAnimation = currentAnimation.subject:CreateAnimation(newLength, newData) currentAnimation.OnComplete = function(...) if (oldOnComplete) then oldOnComplete(...) end newAnimation:Fire() end return newAnimation end if (length == 0 or (!animation.bIgnoreConfig and ix.option.Get("disableAnimations", false))) then animation.Fire = function(anim) anim:set(anim.duration) anim.bShouldPlay = true end else animation.Fire = function(anim) anim:set(0) anim.bShouldPlay = true end end -- we can assume if we're using this library, we're not going to use the built-in -- AnimationTo functions, so override AnimationThink with our own self.AnimationThink = TweenAnimationThink -- fire right away if autofire is enabled if (animation.bAutoFire) then animation:Fire() end self.tweenAnimations[index] = animation return animation end end if (CLIENT) then local panelMeta = FindMetaTable("Panel") ix.util.InstallAnimationMethods(panelMeta) end ================================================ FILE: gamemode/core/libs/sh_anims.lua ================================================ --[[-- Player model animation. Helix comes with support for using NPC animations/models as regular player models by manually translating animations. There are a few standard animation sets that are built-in that should cover most non-player models: citizen_male citizen_female metrocop overwatch vortigaunt player zombie fastZombie If you find that your models are T-posing when they work elsewhere, you'll probably need to set the model class for your model with `ix.anim.SetModelClass` in order for the correct animations to be used. If you'd like to add your own animation class, simply add to the `ix.anim` table with a model class name and the required animation translation table. ]] -- @module ix.anim ix.anim = ix.anim or {} ix.anim.citizen_male = { normal = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, pistol = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_RANGE_ATTACK_PISTOL}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_ATTACK_PISTOL_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_PISTOL, reload = ACT_RELOAD_PISTOL }, smg = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_SMG1, reload = ACT_GESTURE_RELOAD_SMG1 }, shotgun = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, grenade = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_RANGE_ATTACK_THROW }, melee = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_MELEE_ATTACK_SWING }, glide = ACT_GLIDE, vehicle = { ["prop_vehicle_prisoner_pod"] = {"podpose", Vector(-3, 0, 0)}, ["prop_vehicle_jeep"] = {ACT_BUSY_SIT_CHAIR, Vector(14, 0, -14)}, ["prop_vehicle_airboat"] = {ACT_BUSY_SIT_CHAIR, Vector(8, 0, -20)}, chair = {ACT_BUSY_SIT_CHAIR, Vector(1, 0, -23)} }, } ix.anim.citizen_female = { normal = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, pistol = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_PISTOL, reload = ACT_RELOAD_PISTOL }, smg = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_SMG1, reload = ACT_GESTURE_RELOAD_SMG1 }, shotgun = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, grenade = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_RANGE_ATTACK_THROW }, melee = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_MELEE_ATTACK_SWING }, glide = ACT_GLIDE, vehicle = ix.anim.citizen_male.vehicle } ix.anim.metrocop = { normal = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, pistol = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW}, [ACT_MP_WALK] = {ACT_WALK_PISTOL, ACT_WALK_AIM_PISTOL}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN_PISTOL, ACT_RUN_AIM_PISTOL}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_GESTURE_RANGE_ATTACK_PISTOL, reload = ACT_GESTURE_RELOAD_PISTOL }, smg = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, shotgun = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW}, [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, grenade = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_COMBINE_THROW_GRENADE }, melee = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE}, [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_MELEE_ATTACK_SWING_GESTURE }, glide = ACT_GLIDE, vehicle = { chair = {ACT_COVER_PISTOL_LOW, Vector(5, 0, -5)}, ["prop_vehicle_airboat"] = {ACT_COVER_PISTOL_LOW, Vector(10, 0, 0)}, ["prop_vehicle_jeep"] = {ACT_COVER_PISTOL_LOW, Vector(18, -2, 4)}, ["prop_vehicle_prisoner_pod"] = {ACT_IDLE, Vector(-4, -0.5, 0)} } } ix.anim.overwatch = { normal = { [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY}, [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, pistol = { [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, smg = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1}, [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, shotgun = { [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SHOTGUN}, [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_SHOTGUN}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_SHOTGUN}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, grenade = { [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY}, [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, melee = { [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY}, [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, [ACT_LAND] = {ACT_RESET, ACT_RESET}, attack = ACT_MELEE_ATTACK_SWING_GESTURE }, glide = ACT_GLIDE } ix.anim.vortigaunt = { melee = { ["attack"] = ACT_MELEE_ATTACK1, [ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}, }, grenade = { ["attack"] = ACT_MELEE_ATTACK1, [ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK} }, normal = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}, ["attack"] = ACT_MELEE_ATTACK1 }, pistol = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, ["reload"] = ACT_IDLE, [ACT_MP_RUN] = {ACT_RUN, "run_all_TC"}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"} }, shotgun = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, ["reload"] = ACT_IDLE, [ACT_MP_RUN] = {ACT_RUN, "run_all_TC"}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"} }, smg = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, ["reload"] = ACT_IDLE, [ACT_MP_RUN] = {ACT_RUN, "run_all_TC"}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"} }, beam = { [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY}, [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM}, [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}, ["attack"] = ACT_GESTURE_RANGE_ATTACK1, ["reload"] = ACT_IDLE, ["glide"] = {ACT_RUN, ACT_RUN} }, glide = "jump_holding_glide" } ix.anim.player = { normal = { [ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE, [ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH, [ACT_MP_WALK] = ACT_HL2MP_WALK, [ACT_MP_RUN] = ACT_HL2MP_RUN, [ACT_LAND] = {ACT_RESET, ACT_RESET} }, passive = { [ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_PASSIVE, [ACT_MP_WALK] = ACT_HL2MP_WALK_PASSIVE, [ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_PASSIVE, [ACT_MP_RUN] = ACT_HL2MP_RUN_PASSIVE, [ACT_LAND] = {ACT_RESET, ACT_RESET} } } ix.anim.zombie = { [ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_ZOMBIE, [ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE, [ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_01, [ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_02, [ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE, [ACT_LAND] = {ACT_RESET, ACT_RESET} } ix.anim.fastZombie = { [ACT_MP_STAND_IDLE] = ACT_HL2MP_WALK_ZOMBIE, [ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE, [ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_05, [ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_06, [ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE_FAST, [ACT_LAND] = {ACT_RESET, ACT_RESET} } local translations = {} --- Sets a model's animation class. -- @realm shared -- @string model Model name to set the animation class for -- @string class Animation class to assign to the model -- @usage ix.anim.SetModelClass("models/police.mdl", "metrocop") function ix.anim.SetModelClass(model, class) if (!ix.anim[class]) then error("'" .. tostring(class) .. "' is not a valid animation class!") end translations[model:lower()] = class end --- Gets a model's animation class. -- @realm shared -- @string model Model to get the animation class for -- @treturn[1] string Animation class of the model -- @treturn[2] nil If there was no animation associated with the given model -- @usage ix.anim.GetModelClass("models/police.mdl") -- > metrocop function ix.anim.GetModelClass(model) model = string.lower(model) local class = translations[model] if (!class and string.find(model, "/player")) then return "player" end class = class or "citizen_male" if (class == "citizen_male" and ( string.find(model, "female") or string.find(model, "alyx") or string.find(model, "mossman"))) then class = "citizen_female" end return class end ix.anim.SetModelClass("models/police.mdl", "metrocop") ix.anim.SetModelClass("models/combine_super_soldier.mdl", "overwatch") ix.anim.SetModelClass("models/combine_soldier_prisonGuard.mdl", "overwatch") ix.anim.SetModelClass("models/combine_soldier.mdl", "overwatch") ix.anim.SetModelClass("models/vortigaunt.mdl", "vortigaunt") ix.anim.SetModelClass("models/vortigaunt_blue.mdl", "vortigaunt") ix.anim.SetModelClass("models/vortigaunt_doctor.mdl", "vortigaunt") ix.anim.SetModelClass("models/vortigaunt_slave.mdl", "vortigaunt") if (SERVER) then util.AddNetworkString("ixSequenceSet") util.AddNetworkString("ixSequenceReset") local playerMeta = FindMetaTable("Player") --- Player anim methods -- @classmod Player --- Forces this player's model to play an animation sequence. It also prevents the player from firing their weapon while the -- animation is playing. -- @realm server -- @string sequence Name of the animation sequence to play -- @func[opt=nil] callback Function to call when the animation finishes. This is also called immediately if the animation -- fails to play -- @number[opt=nil] time How long to play the animation for. This defaults to the duration of the animation -- @bool[opt=false] bNoFreeze Whether or not to avoid freezing this player in place while the animation is playing -- @see LeaveSequence function playerMeta:ForceSequence(sequence, callback, time, bNoFreeze) hook.Run("PlayerEnterSequence", self, sequence, callback, time, bNoFreeze) if (!sequence) then net.Start("ixSequenceReset") net.WriteEntity(self) net.Broadcast() return end sequence = self:LookupSequence(tostring(sequence)) if (sequence and sequence > 0) then time = time or self:SequenceDuration(sequence) self.ixCouldShoot = self:GetNetVar("canShoot", false) self.ixSeqCallback = callback self:SetCycle(0) self:SetPlaybackRate(1) self:SetNetVar("forcedSequence", sequence) self:SetNetVar("canShoot", false) if (!bNoFreeze) then self:SetMoveType(MOVETYPE_NONE) end if (time > 0) then timer.Create("ixSeq"..self:EntIndex(), time, 1, function() if (IsValid(self)) then self:LeaveSequence() end end) end net.Start("ixSequenceSet") net.WriteEntity(self) net.Broadcast() return time elseif (callback) then callback() end return false end --- Forcefully stops this player's model from playing an animation that was started by `ForceSequence`. -- @realm server function playerMeta:LeaveSequence() hook.Run("PlayerLeaveSequence", self) net.Start("ixSequenceReset") net.WriteEntity(self) net.Broadcast() self:SetNetVar("canShoot", self.ixCouldShoot) self:SetNetVar("forcedSequence", nil) self:SetMoveType(MOVETYPE_WALK) self.ixCouldShoot = nil if (self.ixSeqCallback) then self:ixSeqCallback() end end else net.Receive("ixSequenceSet", function() local entity = net.ReadEntity() if (IsValid(entity)) then hook.Run("PlayerEnterSequence", entity) end end) net.Receive("ixSequenceReset", function() local entity = net.ReadEntity() if (IsValid(entity)) then hook.Run("PlayerLeaveSequence", entity) end end) end ================================================ FILE: gamemode/core/libs/sh_attribs.lua ================================================ -- @module ix.attributes if (!ix.char) then include("sh_character.lua") end ix.attributes = ix.attributes or {} ix.attributes.list = ix.attributes.list or {} function ix.attributes.LoadFromDir(directory) for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do local niceName = v:sub(4, -5) ATTRIBUTE = ix.attributes.list[niceName] or {} if (PLUGIN) then ATTRIBUTE.plugin = PLUGIN.uniqueID end ix.util.Include(directory.."/"..v) ATTRIBUTE.name = ATTRIBUTE.name or "Unknown" ATTRIBUTE.description = ATTRIBUTE.description or "No description availalble." ix.attributes.list[niceName] = ATTRIBUTE ATTRIBUTE = nil end end function ix.attributes.Setup(client) local character = client:GetCharacter() if (character) then for k, v in pairs(ix.attributes.list) do if (v.OnSetup) then v:OnSetup(client, character:GetAttribute(k, 0)) end end end end do --- Character attribute methods -- @classmod Character local charMeta = ix.meta.character if (SERVER) then util.AddNetworkString("ixAttributeUpdate") --- Increments one of this character's attributes by the given amount. -- @realm server -- @string key Name of the attribute to update -- @number value Amount to add to the attribute function charMeta:UpdateAttrib(key, value) local attribute = ix.attributes.list[key] local client = self:GetPlayer() if (attribute) then local attrib = self:GetAttributes() attrib[key] = math.min((attrib[key] or 0) + value, attribute.maxValue or ix.config.Get("maxAttributes", 100)) if (IsValid(client)) then net.Start("ixAttributeUpdate") net.WriteUInt(self:GetID(), 32) net.WriteString(key) net.WriteFloat(attrib[key]) net.Send(client) if (attribute.Setup) then attribute.Setup(attrib[key]) end end self:SetAttributes(attrib) end hook.Run("CharacterAttributeUpdated", client, self, key, value) end --- Sets the value of an attribute for this character. -- @realm server -- @string key Name of the attribute to update -- @number value New value for the attribute function charMeta:SetAttrib(key, value) local attribute = ix.attributes.list[key] local client = self:GetPlayer() if (attribute) then local attrib = self:GetAttributes() attrib[key] = value if (IsValid(client)) then net.Start("ixAttributeUpdate") net.WriteUInt(self:GetID(), 32) net.WriteString(key) net.WriteFloat(attrib[key]) net.Send(client) if (attribute.Setup) then attribute.Setup(attrib[key]) end end self:SetAttributes(attrib) end hook.Run("CharacterAttributeUpdated", client, self, key, value) end --- Temporarily increments one of this character's attributes. Useful for things like consumable items. -- @realm server -- @string boostID Unique ID to use for the boost to remove it later -- @string attribID Name of the attribute to boost -- @number boostAmount Amount to increase the attribute by function charMeta:AddBoost(boostID, attribID, boostAmount) local boosts = self:GetVar("boosts", {}) boosts[attribID] = boosts[attribID] or {} boosts[attribID][boostID] = boostAmount hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, boostAmount) return self:SetVar("boosts", boosts, nil, self:GetPlayer()) end --- Removes a temporary boost from this character. -- @realm server -- @string boostID Unique ID of the boost to remove -- @string attribID Name of the attribute that was boosted function charMeta:RemoveBoost(boostID, attribID) local boosts = self:GetVar("boosts", {}) boosts[attribID] = boosts[attribID] or {} boosts[attribID][boostID] = nil hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, true) return self:SetVar("boosts", boosts, nil, self:GetPlayer()) end else net.Receive("ixAttributeUpdate", function() local id = net.ReadUInt(32) local character = ix.char.loaded[id] if (character) then local key = net.ReadString() local value = net.ReadFloat() character:GetAttributes()[key] = value end end) end --- Returns all boosts that this character has for the given attribute. This is only valid on the server and owning client. -- @realm shared -- @string attribID Name of the attribute to find boosts for -- @treturn[1] table Table of boosts that this character has for the attribute -- @treturn[2] nil If the character has no boosts for the given attribute function charMeta:GetBoost(attribID) local boosts = self:GetBoosts() return boosts[attribID] end --- Returns all boosts that this character has. This is only valid on the server and owning client. -- @realm shared -- @treturn table Table of boosts this character has function charMeta:GetBoosts() return self:GetVar("boosts", {}) end --- Returns the current value of an attribute. This is only valid on the server and owning client. -- @realm shared -- @string key Name of the attribute to get -- @number default Value to return if the attribute doesn't exist -- @treturn number Value of the attribute function charMeta:GetAttribute(key, default) local att = self:GetAttributes()[key] or default local boosts = self:GetBoosts()[key] if (boosts) then for _, v in pairs(boosts) do att = att + v end end return att end end ================================================ FILE: gamemode/core/libs/sh_business.lua ================================================ if (SERVER) then util.AddNetworkString("ixBusinessBuy") util.AddNetworkString("ixBusinessResponse") util.AddNetworkString("ixShipmentUse") util.AddNetworkString("ixShipmentOpen") util.AddNetworkString("ixShipmentClose") net.Receive("ixBusinessBuy", function(length, client) if (client.ixNextBusiness and client.ixNextBusiness > CurTime()) then client:NotifyLocalized("businessTooFast") return end local char = client:GetCharacter() if (!char) then return end local indicies = net.ReadUInt(8) local items = {} for _ = 1, indicies do items[net.ReadString()] = net.ReadUInt(8) end if (table.IsEmpty(items)) then return end local cost = 0 for k, v in pairs(items) do local itemTable = ix.item.list[k] if (itemTable and hook.Run("CanPlayerUseBusiness", client, k) != false) then local amount = math.Clamp(tonumber(v) or 0, 0, 10) items[k] = amount if (amount == 0) then items[k] = nil else cost = cost + (amount * (itemTable.price or 0)) end else items[k] = nil end end if (table.IsEmpty(items)) then return end if (char:HasMoney(cost)) then char:TakeMoney(cost) local entity = ents.Create("ix_shipment") entity:Spawn() entity:SetPos(client:GetItemDropPos(entity)) entity:SetItems(items) entity:SetNetVar("owner", char:GetID()) local shipments = char:GetVar("charEnts") or {} table.insert(shipments, entity) char:SetVar("charEnts", shipments, true) net.Start("ixBusinessResponse") net.Send(client) hook.Run("CreateShipment", client, entity) client.ixNextBusiness = CurTime() + 0.5 end end) net.Receive("ixShipmentUse", function(length, client) local uniqueID = net.ReadString() local drop = net.ReadBool() local entity = client.ixShipment local itemTable = ix.item.list[uniqueID] if (itemTable and IsValid(entity)) then if (entity:GetPos():Distance(client:GetPos()) > 128) then client.ixShipment = nil return end local amount = entity.items[uniqueID] if (amount and amount > 0) then if (entity.items[uniqueID] <= 0) then entity.items[uniqueID] = nil end if (drop) then ix.item.Spawn(uniqueID, entity:GetPos() + Vector(0, 0, 16), function(item, itemEntity) if (IsValid(client)) then itemEntity.ixSteamID = client:SteamID() itemEntity.ixCharID = client:GetCharacter():GetID() end end) else local status, _ = client:GetCharacter():GetInventory():Add(uniqueID) if (!status) then return client:NotifyLocalized("noFit") end end hook.Run("ShipmentItemTaken", client, uniqueID, amount) entity.items[uniqueID] = entity.items[uniqueID] - 1 if (entity:GetItemCount() < 1) then entity:GibBreakServer(Vector(0, 0, 0.5)) entity:Remove() end end end end) net.Receive("ixShipmentClose", function(length, client) local entity = client.ixShipment if (IsValid(entity)) then entity.ixInteractionDirty = false client.ixShipment = nil end end) else net.Receive("ixShipmentOpen", function() local entity = net.ReadEntity() local items = net.ReadTable() ix.gui.shipment = vgui.Create("ixShipment") ix.gui.shipment:SetItems(entity, items) end) end ================================================ FILE: gamemode/core/libs/sh_character.lua ================================================ --[[-- Character creation and management. **NOTE:** For the most part you shouldn't use this library unless you know what you're doing. You can very easily corrupt character data using these functions! ]] -- @module ix.char ix.char = ix.char or {} --- Characters that are currently loaded into memory. This is **not** a table of characters that players are currently using. -- Characters are automatically loaded when a player joins the server. Entries are not cleared once the player disconnects, as -- some data is needed after the player has disconnected. Clients will also keep their own version of this table, so don't -- expect it to be the same as the server's. -- -- The keys in this table are the IDs of characters, and the values are the `Character` objects that the ID corresponds to. -- @realm shared -- @table ix.char.loaded -- @usage print(ix.char.loaded[1]) -- > character[1] ix.char.loaded = ix.char.loaded or {} --- Variables that are stored on characters. This table is populated automatically by `ix.char.RegisterVar`. -- @realm shared -- @table ix.char.vars -- @usage print(ix.char.vars["name"]) -- > table: 0xdeadbeef ix.char.vars = ix.char.vars or {} --- Functions similar to `ix.char.loaded`, but is serverside only. This contains a table of all loaded characters grouped by -- the SteamID64 of the player that owns them. -- @realm server -- @table ix.char.cache ix.char.cache = ix.char.cache or {} ix.util.Include("helix/gamemode/core/meta/sh_character.lua") if (SERVER) then --- Creates a character object with its assigned properties and saves it to the database. -- @realm server -- @tab data Properties to assign to this character. If fields are missing from the table, then it will use the default -- value for that property -- @func callback Function to call after the character saves function ix.char.Create(data, callback) local timeStamp = math.floor(os.time()) data.money = data.money or ix.config.Get("defaultMoney", 0) data.schema = Schema and Schema.folder or "helix" data.createTime = timeStamp data.lastJoinTime = timeStamp local query = mysql:Insert("ix_characters") query:Insert("name", data.name or "") query:Insert("description", data.description or "") query:Insert("model", data.model or "models/error.mdl") query:Insert("schema", Schema and Schema.folder or "helix") query:Insert("create_time", data.createTime) query:Insert("last_join_time", data.lastJoinTime) query:Insert("steamid", data.steamID) query:Insert("faction", data.faction or "Unknown") query:Insert("money", data.money) query:Insert("data", util.TableToJSON(data.data or {})) query:Callback(function(result, status, lastID) local invQuery = mysql:Insert("ix_inventories") invQuery:Insert("character_id", lastID) invQuery:Callback(function(invResult, invStats, invLastID) local client = player.GetBySteamID64(data.steamID) ix.char.RestoreVars(data, data) local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight") local character = ix.char.New(data, lastID, client, data.steamID) local inventory = ix.inventory.Create(w, h, invLastID) character.vars.inv = {inventory} inventory:SetOwner(lastID) ix.char.loaded[lastID] = character table.insert(ix.char.cache[data.steamID], lastID) if (callback) then callback(lastID) end end) invQuery:Execute() end) query:Execute() end --- Loads all of a player's characters into memory. -- @realm server -- @player client Player to load the characters for -- @func[opt=nil] callback Function to call when the characters have been loaded -- @bool[opt=false] bNoCache Whether or not to skip the cache; players that leave and join again later will already have -- their characters loaded which will skip the database query and load quicker -- @number[opt=nil] id The ID of a specific character to load instead of all of the player's characters function ix.char.Restore(client, callback, bNoCache, id) local steamID64 = client:SteamID64() local cache = ix.char.cache[steamID64] if (cache and !bNoCache) then for _, v in ipairs(cache) do local character = ix.char.loaded[v] if (character and !IsValid(character.client)) then character.player = client end end if (callback) then callback(cache) end return end local query = mysql:Select("ix_characters") query:Select("id") ix.char.RestoreVars(query) query:Where("schema", Schema.folder) query:Where("steamid", steamID64) if (id) then query:Where("id", id) end query:Callback(function(result) local characters = {} for _, v in ipairs(result or {}) do local charID = tonumber(v.id) if (charID) then local data = { steamID = steamID64 } ix.char.RestoreVars(data, v) characters[#characters + 1] = charID local character = ix.char.New(data, charID, client) hook.Run("CharacterRestored", character) character.vars.inv = { [1] = -1, } local invQuery = mysql:Select("ix_inventories") invQuery:Select("inventory_id") invQuery:Select("inventory_type") invQuery:Where("character_id", charID) invQuery:Callback(function(info) if (istable(info) and #info > 0) then local inventories = {} for _, v2 in pairs(info) do if (v2.inventory_type and isstring(v2.inventory_type) and v2.inventory_type == "NULL") then v2.inventory_type = nil end if (hook.Run("ShouldRestoreInventory", charID, v2.inventory_id, v2.inventory_type) != false) then local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight") local invType if (v2.inventory_type) then invType = ix.item.inventoryTypes[v2.inventory_type] if (invType) then w, h = invType.w, invType.h end end inventories[tonumber(v2.inventory_id)] = {w, h, v2.inventory_type} end end ix.inventory.Restore(inventories, nil, nil, function(inventory) local inventoryType = inventories[inventory:GetID()][3] if (inventoryType) then inventory.vars.isBag = inventoryType table.insert(character.vars.inv, inventory) else character.vars.inv[1] = inventory end inventory:SetOwner(charID) end, true) else local insertQuery = mysql:Insert("ix_inventories") insertQuery:Insert("character_id", charID) insertQuery:Callback(function(_, status, lastID) local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight") local inventory = ix.inventory.Create(w, h, lastID) inventory:SetOwner(charID) character.vars.inv = { inventory } end) insertQuery:Execute() end end) invQuery:Execute() ix.char.loaded[charID] = character else ErrorNoHalt("[Helix] Attempt to load character with invalid ID '" .. tostring(id) .. "'!") end end if (callback) then callback(characters) end ix.char.cache[steamID64] = characters end) query:Execute() end --- Adds character properties to a table. This is done automatically by `ix.char.Restore`, so that should be used instead if -- you are loading characters. -- @realm server -- @internal -- @tab data Table of fields to apply to the table. If this is an SQL query object, it will instead populate the query with -- `SELECT` statements for each applicable character var in `ix.char.vars`. -- @tab characterInfo Table to apply the properties to. This can be left as `nil` if an SQL query object is passed in `data` function ix.char.RestoreVars(data, characterInfo) if (data.queryType) then -- populate query for _, v in pairs(ix.char.vars) do if (v.field and v.fieldType and !v.bSaveLoadInitialOnly) then data:Select(v.field) -- if FilterValues is used, any rows that contain a value in the column that isn't in the valid values table -- will be ignored entirely (i.e the character will not load if it has an invalid value) if (v.FilterValues) then data:WhereIn(v.field, v:FilterValues()) end end end else -- populate character data for k, v in pairs(ix.char.vars) do if (v.field and characterInfo[v.field] and !v.bSaveLoadInitialOnly) then local value = characterInfo[v.field] if (isnumber(v.default)) then value = tonumber(value) or v.default elseif (isstring(v.default)) then value = tostring(value) == "NULL" and v.default or tostring(value or v.default) elseif (isbool(v.default)) then if (tostring(value) != "NULL") then value = tobool(value) else value = v.default end elseif (istable(v.default)) then value = istable(value) and value or util.JSONToTable(value) end data[k] = value end end end end end --- Creates a new empty `Character` object. If you are looking to create a usable character, see `ix.char.Create`. -- @realm shared -- @internal -- @tab data Character vars to assign -- @number id Unique ID of the character -- @player client Player that will own the character -- @string[opt=client:SteamID64()] steamID SteamID64 of the player that will own the character function ix.char.New(data, id, client, steamID) if (data.name) then data.name = data.name:gsub("#", "#​") end if (data.description) then data.description = data.description:gsub("#", "#​") end local character = setmetatable({vars = {}}, ix.meta.character) for k, v in pairs(data) do if (v != nil) then character.vars[k] = v end end character.id = id or 0 character.player = client if (SERVER and IsValid(client) or steamID) then character.steamID = IsValid(client) and client:SteamID64() or steamID end return character end ix.char.varHooks = ix.char.varHooks or {} function ix.char.HookVar(varName, hookName, func) ix.char.varHooks[varName] = ix.char.varHooks[varName] or {} ix.char.varHooks[varName][hookName] = func end do --- Default character vars -- @classmod Character --- Sets this character's name. This is automatically networked. -- @realm server -- @string name New name for the character -- @function SetName --- Returns this character's name -- @realm shared -- @treturn string This character's current name -- @function GetName ix.char.RegisterVar("name", { field = "name", fieldType = ix.type.string, default = "John Doe", index = 1, OnValidate = function(self, value, payload, client) if (!value) then return false, "invalid", "name" end value = tostring(value):gsub("\r\n", ""):gsub("\n", "") value = string.Trim(value) local minLength = ix.config.Get("minNameLength", 4) local maxLength = ix.config.Get("maxNameLength", 32) if (value:utf8len() < minLength) then return false, "nameMinLen", minLength elseif (!value:find("%S")) then return false, "invalid", "name" elseif (value:gsub("%s", ""):utf8len() > maxLength) then return false, "nameMaxLen", maxLength end return hook.Run("GetDefaultCharacterName", client, payload.faction) or value:utf8sub(1, 70) end, OnPostSetup = function(self, panel, payload) local faction = ix.faction.indices[payload.faction] local name, disabled = hook.Run("GetDefaultCharacterName", LocalPlayer(), payload.faction) if (name) then panel:SetText(name) payload:Set("name", name) end if (disabled) then panel:SetDisabled(true) panel:SetEditable(false) end panel:SetBackgroundColor(faction.color or Color(255, 255, 255, 25)) end }) --- Sets this character's physical description. This is automatically networked. -- @realm server -- @string description New description for this character -- @function SetDescription --- Returns this character's physical description. -- @realm shared -- @treturn string This character's current description -- @function GetDescription ix.char.RegisterVar("description", { field = "description", fieldType = ix.type.text, default = "", index = 2, OnValidate = function(self, value, payload) value = string.Trim((tostring(value):gsub("\r\n", ""):gsub("\n", ""))) local minLength = ix.config.Get("minDescriptionLength", 16) if (value:utf8len() < minLength) then return false, "descMinLen", minLength elseif (!value:find("%s+") or !value:find("%S")) then return false, "invalid", "description" end return value end, OnPostSetup = function(self, panel, payload) panel:SetMultiline(true) panel:SetFont("ixMenuButtonFont") panel:SetTall(panel:GetTall() * 2 + 6) -- add another line panel.AllowInput = function(_, character) if (character == "\n" or character == "\r") then return true end end end, alias = "Desc" }) --- Sets this character's model. This sets the player's current model to the given one, and saves it to the character. -- It is automatically networked. -- @realm server -- @string model New model for the character -- @function SetModel --- Returns this character's model. -- @realm shared -- @treturn string This character's current model -- @function GetModel ix.char.RegisterVar("model", { field = "model", fieldType = ix.type.string, default = "models/error.mdl", index = 3, OnSet = function(character, value) local client = character:GetPlayer() if (IsValid(client) and client:GetCharacter() == character) then client:SetModel(value) end character.vars.model = value end, OnGet = function(character, default) return character.vars.model or default end, OnDisplay = function(self, container, payload) local scroll = container:Add("DScrollPanel") scroll:Dock(FILL) -- TODO: don't fill so we can allow other panels scroll.Paint = function(panel, width, height) derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, Color(255, 255, 255, 25)) end local layout = scroll:Add("DIconLayout") layout:Dock(FILL) layout:SetSpaceX(1) layout:SetSpaceY(1) local faction = ix.faction.indices[payload.faction] if (faction) then local models = faction:GetModels(LocalPlayer()) for k, v in SortedPairs(models) do local icon = layout:Add("SpawnIcon") icon:SetSize(64, 128) icon:InvalidateLayout(true) icon.DoClick = function(this) payload:Set("model", k) end icon.PaintOver = function(this, w, h) if (payload.model == k) then local color = ix.config.Get("color", color_white) surface.SetDrawColor(color.r, color.g, color.b, 200) for i = 1, 3 do local i2 = i * 2 surface.DrawOutlinedRect(i, i, w - i2, h - i2) end end end if (isstring(v)) then icon:SetModel(v) else icon:SetModel(v[1], v[2] or 0, v[3]) end end end return scroll end, OnValidate = function(self, value, payload, client) local faction = ix.faction.indices[payload.faction] if (faction) then local models = faction:GetModels(client) if (!payload.model or !models[payload.model]) then return false, "needModel" end else return false, "needModel" end end, OnAdjust = function(self, client, data, value, newData) local faction = ix.faction.indices[data.faction] if (faction) then local model = faction:GetModels(client)[value] if (isstring(model)) then newData.model = model elseif (istable(model)) then newData.model = model[1] -- save skin/bodygroups to character data local bodygroups = {} for i = 1, #model[3] do bodygroups[i - 1] = tonumber(model[3][i]) or 0 end newData.data = newData.data or {} newData.data.skin = model[2] or 0 newData.data.groups = bodygroups end end end, ShouldDisplay = function(self, container, payload) local faction = ix.faction.indices[payload.faction] return #faction:GetModels(LocalPlayer()) > 1 end }) -- SetClass shouldn't be used here, character:JoinClass should be used instead --- Returns this character's current class. -- @realm shared -- @treturn number Index of the class this character is in -- @function GetClass ix.char.RegisterVar("class", { bNoDisplay = true, }) --- Sets this character's faction. Note that this doesn't do the initial setup for the player after the faction has been -- changed, so you'll have to update some character vars manually. -- @realm server -- @number faction Index of the faction to transfer this character to -- @function SetFaction --- Returns this character's faction. -- @realm shared -- @treturn number Index of the faction this character is currently in -- @function GetFaction ix.char.RegisterVar("faction", { field = "faction", fieldType = ix.type.string, default = "Citizen", bNoDisplay = true, FilterValues = function(self) -- make sequential table of faction unique IDs local values = {} for k, v in ipairs(ix.faction.indices) do values[k] = v.uniqueID end return values end, OnSet = function(self, value) local client = self:GetPlayer() if (IsValid(client)) then local oldVar = self:GetFaction() self.vars.faction = ix.faction.indices[value] and ix.faction.indices[value].uniqueID client:SetTeam(value) -- @todo refactor networking of character vars so this doesn't need to be repeated on every OnSet override net.Start("ixCharacterVarChanged") net.WriteUInt(self:GetID(), 32) net.WriteString("faction") net.WriteType(self.vars.faction) net.Broadcast() hook.Run("CharacterVarChanged", self, "faction", oldVar, value) end end, OnGet = function(self, default) local faction = ix.faction.teams[self.vars.faction] return faction and faction.index or 0 end, OnValidate = function(self, index, data, client) if (index and client:HasWhitelist(index)) then return true end return false end, OnAdjust = function(self, client, data, value, newData) newData.faction = ix.faction.indices[value].uniqueID end }) -- attribute manipulation should be done with methods from the ix.attributes library ix.char.RegisterVar("attributes", { field = "attributes", fieldType = ix.type.text, default = {}, index = 4, category = "attributes", isLocal = true, OnDisplay = function(self, container, payload) local maximum = hook.Run("GetDefaultAttributePoints", LocalPlayer(), payload) or 10 if (maximum < 1) then return end local attributes = container:Add("DPanel") attributes:Dock(TOP) local y local total = 0 payload.attributes = {} -- total spendable attribute points local totalBar = attributes:Add("ixAttributeBar") totalBar:SetMax(maximum) totalBar:SetValue(maximum) totalBar:Dock(TOP) totalBar:DockMargin(2, 2, 2, 2) totalBar:SetText(L("attribPointsLeft")) totalBar:SetReadOnly(true) totalBar:SetColor(Color(20, 120, 20, 255)) y = totalBar:GetTall() + 4 for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do payload.attributes[k] = 0 local bar = attributes:Add("ixAttributeBar") bar:SetMax(v.maxValue or maximum) bar:Dock(TOP) bar:DockMargin(2, 2, 2, 2) bar:SetText(L(v.name)) bar.OnChanged = function(this, difference) if ((total + difference) > maximum) then return false end total = total + difference payload.attributes[k] = payload.attributes[k] + difference totalBar:SetValue(totalBar.value - difference) end if (v.noStartBonus) then bar:SetReadOnly() end y = y + bar:GetTall() + 4 end attributes:SetTall(y) return attributes end, OnValidate = function(self, value, data, client) if (value != nil) then if (istable(value)) then local count = 0 for _, v in pairs(value) do count = count + v end if (count > (hook.Run("GetDefaultAttributePoints", client, count) or 10)) then return false, "unknownError" end else return false, "unknownError" end end end, ShouldDisplay = function(self, container, payload) return !table.IsEmpty(ix.attributes.list) end }) --- Sets this character's current money. Money is only networked to the player that owns this character. -- @realm server -- @number money New amount of money this character should have -- @function SetMoney --- Returns this character's money. This is only valid on the server and the owning client. -- @realm shared -- @treturn number Current money of this character -- @function GetMoney ix.char.RegisterVar("money", { field = "money", fieldType = ix.type.number, default = 0, isLocal = true, bNoDisplay = true }) --- Sets a data field on this character. This is useful for storing small bits of data that you need persisted on this -- character. This is networked only to the owning client. If you are going to be accessing this data field frequently with -- a getter/setter, consider using `ix.char.RegisterVar` instead. -- @realm server -- @string key Name of the field that holds the data -- @param value Any value to store in the field, as long as it's supported by GMod's JSON parser -- @function SetData --- Returns a data field set on this character. If it doesn't exist, it will return the given default or `nil`. This is only -- valid on the server and the owning client. -- @realm shared -- @string key Name of the field that's holding the data -- @param default Value to return if the given key doesn't exist, or is `nil` -- @return[1] Data stored in the field -- @treturn[2] nil If the data doesn't exist, or is `nil` -- @function GetData ix.char.RegisterVar("data", { default = {}, isLocal = true, bNoDisplay = true, field = "data", fieldType = ix.type.text, OnSet = function(character, key, value, noReplication, receiver) local data = character:GetData() local client = character:GetPlayer() data[key] = value if (!noReplication and IsValid(client)) then net.Start("ixCharacterData") net.WriteUInt(character:GetID(), 32) net.WriteString(key) net.WriteType(value) net.Send(receiver or client) end character.vars.data = data end, OnGet = function(character, key, default) local data = character.vars.data or {} if (key) then if (!data) then return default end local value = data[key] return value == nil and default or value else return default or data end end }) ix.char.RegisterVar("var", { default = {}, bNoDisplay = true, OnSet = function(character, key, value, noReplication, receiver) local data = character:GetVar() local client = character:GetPlayer() data[key] = value if (!noReplication and IsValid(client)) then local id if (client:GetCharacter() and client:GetCharacter():GetID() == character:GetID()) then id = client:GetCharacter():GetID() else id = character:GetID() end net.Start("ixCharacterVar") net.WriteUInt(id, 32) net.WriteString(key) net.WriteType(value) net.Send(receiver or client) end character.vars.vars = data end, OnGet = function(character, key, default) character.vars.vars = character.vars.vars or {} local data = character.vars.vars or {} if (key) then if (!data) then return default end local value = data[key] return value == nil and default or value else return default or data end end }) --- Returns the Unix timestamp of when this character was created (i.e the value of `os.time()` at the time of creation). -- @realm server -- @treturn number Unix timestamp of when this character was created -- @function GetCreateTime ix.char.RegisterVar("createTime", { field = "create_time", fieldType = ix.type.number, bNoDisplay = true, bNoNetworking = true, bNotModifiable = true }) --- Returns the Unix timestamp of when this character was last used by its owning player. -- @realm server -- @treturn number Unix timestamp of when this character was last used -- @function GetLastJoinTime ix.char.RegisterVar("lastJoinTime", { field = "last_join_time", fieldType = ix.type.number, bNoDisplay = true, bNoNetworking = true, bNotModifiable = true, bSaveLoadInitialOnly = true }) --- Returns the schema that this character belongs to. This is useful if you are running multiple schemas off of the same -- database, and need to differentiate between them. -- @realm server -- @treturn string Schema this character belongs to -- @function GetSchema ix.char.RegisterVar("schema", { field = "schema", fieldType = ix.type.string, bNoDisplay = true, bNoNetworking = true, bNotModifiable = true, bSaveLoadInitialOnly = true }) --- Returns the 64-bit Steam ID of the player that owns this character. -- @realm server -- @treturn string Owning player's Steam ID -- @function GetSteamID ix.char.RegisterVar("steamID", { field = "steamid", fieldType = ix.type.steamid, bNoDisplay = true, bNoNetworking = true, bNotModifiable = true, bSaveLoadInitialOnly = true }) end -- Networking information here. do if (SERVER) then util.AddNetworkString("ixCharacterMenu") util.AddNetworkString("ixCharacterChoose") util.AddNetworkString("ixCharacterCreate") util.AddNetworkString("ixCharacterDelete") util.AddNetworkString("ixCharacterLoaded") util.AddNetworkString("ixCharacterLoadFailure") util.AddNetworkString("ixCharacterAuthed") util.AddNetworkString("ixCharacterAuthFailed") util.AddNetworkString("ixCharacterInfo") util.AddNetworkString("ixCharacterData") util.AddNetworkString("ixCharacterKick") util.AddNetworkString("ixCharacterSet") util.AddNetworkString("ixCharacterVar") util.AddNetworkString("ixCharacterVarChanged") net.Receive("ixCharacterChoose", function(length, client) local id = net.ReadUInt(32) if (client:GetCharacter() and client:GetCharacter():GetID() == id) then net.Start("ixCharacterLoadFailure") net.WriteString("@usingChar") net.Send(client) return end local character = ix.char.loaded[id] if (character and character:GetPlayer() == client) then local status, result = hook.Run("CanPlayerUseCharacter", client, character) if (status == false) then net.Start("ixCharacterLoadFailure") net.WriteString(result or "") net.Send(client) return end local currentChar = client:GetCharacter() if (currentChar) then currentChar:Save() for _, v in ipairs(currentChar:GetInventory(true)) do if (istable(v)) then v:RemoveReceiver(client) end end end hook.Run("PrePlayerLoadedCharacter", client, character, currentChar) character:Setup() client:Spawn() hook.Run("PlayerLoadedCharacter", client, character, currentChar) else net.Start("ixCharacterLoadFailure") net.WriteString("@unknownError") net.Send(client) ErrorNoHalt("[Helix] Attempt to load invalid character '" .. id .. "'\n") end end) net.Receive("ixCharacterCreate", function(length, client) if ((client.ixNextCharacterCreate or 0) > RealTime()) then return end local maxChars = hook.Run("GetMaxPlayerCharacter", client) or ix.config.Get("maxCharacters", 5) local charList = client.ixCharList local charCount = table.Count(charList) if (charCount >= maxChars) then net.Start("ixCharacterAuthFailed") net.WriteString("maxCharacters") net.WriteTable({}) net.Send(client) return end client.ixNextCharacterCreate = RealTime() + 1 local indicies = net.ReadUInt(8) local payload = {} for _ = 1, indicies do payload[net.ReadString()] = net.ReadType() end local newPayload = {} local results = {hook.Run("CanPlayerCreateCharacter", client, payload)} if (table.remove(results, 1) == false) then net.Start("ixCharacterAuthFailed") net.WriteString(table.remove(results, 1) or "unknownError") net.WriteTable(results) net.Send(client) return end for k, _ in pairs(payload) do local info = ix.char.vars[k] if (!info or (!info.OnValidate and info.bNoDisplay)) then payload[k] = nil end end for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do local value = payload[k] if (v.OnValidate) then local result = {v:OnValidate(value, payload, client)} if (result[1] == false) then local fault = result[2] table.remove(result, 2) table.remove(result, 1) net.Start("ixCharacterAuthFailed") net.WriteString(fault) net.WriteTable(result) net.Send(client) return else if (result[1] != nil) then payload[k] = result[1] end if (v.OnAdjust) then v:OnAdjust(client, payload, value, newPayload) end end end end payload.steamID = client:SteamID64() hook.Run("AdjustCreationPayload", client, payload, newPayload) payload = table.Merge(payload, newPayload) ix.char.Create(payload, function(id) if (IsValid(client)) then ix.char.loaded[id]:Sync(client) net.Start("ixCharacterAuthed") net.WriteUInt(id, 32) net.WriteUInt(#client.ixCharList, 6) for _, v in ipairs(client.ixCharList) do net.WriteUInt(v, 32) end net.Send(client) MsgN("Created character '" .. id .. "' for " .. client:SteamName() .. ".") hook.Run("OnCharacterCreated", client, ix.char.loaded[id]) end end) end) net.Receive("ixCharacterDelete", function(length, client) local id = net.ReadUInt(32) local character = ix.char.loaded[id] local steamID = client:SteamID64() local isCurrentChar = client:GetCharacter() and client:GetCharacter():GetID() == id if (character and character.steamID == steamID) then for k, v in ipairs(client.ixCharList or {}) do if (v == id) then table.remove(client.ixCharList, k) end end hook.Run("PreCharacterDeleted", client, character) ix.char.loaded[id] = nil net.Start("ixCharacterDelete") net.WriteUInt(id, 32) net.Broadcast() -- remove character from database local query = mysql:Delete("ix_characters") query:Where("id", id) query:Where("steamid", client:SteamID64()) query:Execute() -- DBTODO: setup relations instead -- remove inventory from database query = mysql:Select("ix_inventories") query:Select("inventory_id") query:Where("character_id", id) query:Callback(function(result) if (istable(result)) then -- remove associated items from database for _, v in ipairs(result) do local itemQuery = mysql:Delete("ix_items") itemQuery:Where("inventory_id", v.inventory_id) itemQuery:Execute() ix.item.inventories[tonumber(v.inventory_id)] = nil end end local invQuery = mysql:Delete("ix_inventories") invQuery:Where("character_id", id) invQuery:Execute() end) query:Execute() -- other plugins might need to deal with deleted characters. hook.Run("CharacterDeleted", client, id, isCurrentChar) if (isCurrentChar) then client:SetNetVar("char", nil) client:KillSilent() client:StripAmmo() end end end) else net.Receive("ixCharacterInfo", function() local data = net.ReadTable() local id = net.ReadUInt(32) local client = net.ReadUInt(8) ix.char.loaded[id] = ix.char.New(data, id, client) end) net.Receive("ixCharacterVarChanged", function() local id = net.ReadUInt(32) local character = ix.char.loaded[id] if (character) then local key = net.ReadString() local value = net.ReadType() character.vars[key] = value end end) -- Used for setting random access vars on the "var" character var (really stupid). -- Clean this up someday. net.Receive("ixCharacterVar", function() local id = net.ReadUInt(32) local character = ix.char.loaded[id] if (character) then local key = net.ReadString() local value = net.ReadType() local oldVar = character:GetVar()[key] character:GetVar()[key] = value hook.Run("CharacterVarChanged", character, key, oldVar, value) end end) net.Receive("ixCharacterMenu", function() local indices = net.ReadUInt(6) local charList = {} for _ = 1, indices do charList[#charList + 1] = net.ReadUInt(32) end if (charList) then ix.characters = charList end vgui.Create("ixCharMenu") end) net.Receive("ixCharacterLoadFailure", function() local message = net.ReadString() if (isstring(message) and message:sub(1, 1) == "@") then message = L(message:sub(2)) end message = message != "" and message or L("unknownError") if (IsValid(ix.gui.characterMenu)) then ix.gui.characterMenu:OnCharacterLoadFailed(message) else ix.util.Notify(message) end end) net.Receive("ixCharacterData", function() local id = net.ReadUInt(32) local key = net.ReadString() local value = net.ReadType() local character = ix.char.loaded[id] if (character) then character.vars.data = character.vars.data or {} character:GetData()[key] = value end end) net.Receive("ixCharacterDelete", function() local id = net.ReadUInt(32) local isCurrentChar = LocalPlayer():GetCharacter() and LocalPlayer():GetCharacter():GetID() == id local character = ix.char.loaded[id] ix.char.loaded[id] = nil for k, v in ipairs(ix.characters) do if (v == id) then table.remove(ix.characters, k) if (IsValid(ix.gui.characterMenu)) then ix.gui.characterMenu:OnCharacterDeleted(character) end end end if (isCurrentChar and !IsValid(ix.gui.characterMenu)) then vgui.Create("ixCharMenu") end end) net.Receive("ixCharacterKick", function() local isCurrentChar = net.ReadBool() if (ix.gui.menu and ix.gui.menu:IsVisible()) then ix.gui.menu:Remove() end if (!IsValid(ix.gui.characterMenu)) then vgui.Create("ixCharMenu") elseif (ix.gui.characterMenu:IsClosing()) then ix.gui.characterMenu:Remove() vgui.Create("ixCharMenu") end if (isCurrentChar) then ix.gui.characterMenu.mainPanel:UpdateReturnButton(false) end end) net.Receive("ixCharacterLoaded", function() hook.Run("CharacterLoaded", ix.char.loaded[net.ReadUInt(32)]) end) end end do --- Character util functions for player -- @classmod Player local playerMeta = FindMetaTable("Player") playerMeta.SteamName = playerMeta.SteamName or playerMeta.Name --- Returns this player's currently possessed `Character` object if it exists. -- @realm shared -- @treturn[1] Character Currently loaded character -- @treturn[2] nil If this player has no character loaded function playerMeta:GetCharacter() return ix.char.loaded[self:GetNetVar("char")] end playerMeta.GetChar = playerMeta.GetCharacter --- Returns this player's current name. -- @realm shared -- @treturn[1] string Name of this player's currently loaded character -- @treturn[2] string Steam name of this player if the player has no character loaded function playerMeta:GetName() local character = self:GetCharacter() return character and character:GetName() or self:SteamName() end playerMeta.Nick = playerMeta.GetName playerMeta.Name = playerMeta.GetName end ================================================ FILE: gamemode/core/libs/sh_chatbox.lua ================================================ --[[-- Chat manipulation and helper functions. Chat messages are a core part of the framework - it's takes up a good chunk of the gameplay, and is also used to interact with the framework. Chat messages can have types or "classes" that describe how the message should be interpreted. All chat messages will have some type of class: `ic` for regular in-character speech, `me` for actions, `ooc` for out-of-character, etc. These chat classes can affect how the message is displayed in each player's chatbox. See `ix.chat.Register` and `ChatClassStructure` to create your own chat classes. ]] -- @module ix.chat ix.chat = ix.chat or {} --- List of all chat classes that have been registered by the framework, where each key is the name of the chat class, and value -- is the chat class data. Accessing a chat class's data is useful for when you want to copy some functionality or properties -- to use in your own. Note that if you're accessing this table, you should do so inside of the `InitializedChatClasses` hook. -- @realm shared -- @table ix.chat.classes -- @usage print(ix.chat.classes.ic.format) -- > "%s says \"%s\"" ix.chat.classes = ix.chat.classes or {} if (!ix.command) then include("sh_command.lua") end CAMI.RegisterPrivilege({ Name = "Helix - Bypass OOC Timer", MinAccess = "admin" }) -- note we can't use commas in the "color" field's default value since the metadata is separated by commas which will break the -- formatting for that field --- Chat messages can have different classes or "types" of messages that have different properties. This can include how the -- text is formatted, color, hearing distance, etc. -- @realm shared -- @table ChatClassStructure -- @see ix.chat.Register -- @field[type=string] prefix What the player must type before their message in order to use this chat class. For example, -- having a prefix of `/Y` will require to type `/Y I am yelling` in order to send a message with this chat class. This can also -- be a table of strings if you want to allow multiple prefixes, such as `{"//", "/OOC"}`. -- -- **NOTE:** the prefix should usually start with a `/` to be consistent with the rest of the framework. However, you are able -- to use something different like the `LOOC` chat class where the prefixes are `.//`, `[[`, and `/LOOC`. -- @field[type=bool,opt=false] noSpaceAfter Whether or not the `prefix` can be used without a space after it. For example, the -- `OOC` chat class allows you to type `//my message` instead of `// my message`. **NOTE:** this only works if the last -- character in the prefix is non-alphanumeric (i.e `noSpaceAfter` with `/Y` will not work, but `/!` will). -- @field[type=string,opt] description Description to show to the user in the chatbox when they're using this chat class -- @field[type=string,opt="%s: \"%s\""] format How to format a message with this chat class. The first `%s` will be the speaking -- player's name, and the second one will be their message -- @field[type=color,opt=Color(242 230 160)] color Color to use when displaying a message with this chat class -- @field[type=string,opt="chatTyping"] indicator Language phrase to use when displaying the typing indicator above the -- speaking player's head -- @field[type=bool,opt=false] bNoIndicator Whether or not to avoid showing the typing indicator above the speaking player's -- head -- @field[type=string,opt=ixChatFont] font Font to use for displaying a message with this chat class -- @field[type=bool,opt=false] deadCanChat Whether or not a dead player can send a message with this chat class -- @field[type=number] CanHear This can be either a `number` representing how far away another player can hear this message. -- IC messages will use the `chatRange` config, for example. This can also be a function, which returns `true` if the given -- listener can hear the message emitted from a speaker. -- -- message can be heard by any player 1000 units away from the speaking player -- CanHear = 1000 -- OR -- CanHear = function(self, speaker, listener) -- -- the speaking player will be heard by everyone -- return true -- end -- @field[type=function,opt] CanSay Function to run to check whether or not a player can send a message with this chat class. -- By default, it will return `false` if the player is dead and `deadCanChat` is `false`. Overriding this function will prevent -- `deadCanChat` from working, and you must implement this functionality manually. -- CanSay = function(self, speaker, text) -- -- the speaker will never be able to send a message with this chat class -- return false -- end -- @field[type=function,opt] GetColor Function to run to set the color of a message with this chat class. You should generally -- stick to using `color`, but this is useful for when you want the color of the message to change with some criteria. -- GetColor = function(self, speaker, text) -- -- each message with this chat class will be colored a random shade of red -- return Color(math.random(120, 200), 0, 0) -- end -- @field[type=function,opt] OnChatAdd Function to run when a message with this chat class should be added to the chatbox. If -- using this function, make sure you end the function by calling `chat.AddText` in order for the text to show up. -- -- **NOTE:** using your own `OnChatAdd` function will prevent `color`, `GetColor`, or `format` from being used since you'll be -- overriding the base function that uses those properties. In such cases you'll need to add that functionality back in -- manually. In general, you should avoid overriding this function where possible. The `data` argument in the function is -- whatever is passed into the same `data` argument in `ix.chat.Send`. -- -- OnChatAdd = function(self, speaker, text, bAnonymous, data) -- -- adds white text in the form of "Player Name: Message contents" -- chat.AddText(color_white, speaker:GetName(), ": ", text) -- end --- Registers a new chat type with the information provided. Chat classes should usually be created inside of the -- `InitializedChatClasses` hook. -- @realm shared -- @string chatType Name of the chat type -- @tparam ChatClassStructure data Properties and functions to assign to this chat class -- @usage -- this is the "me" chat class taken straight from the framework as an example -- ix.chat.Register("me", { -- format = "** %s %s", -- color = Color(255, 50, 50), -- CanHear = ix.config.Get("chatRange", 280) * 2, -- prefix = {"/Me", "/Action"}, -- description = "@cmdMe", -- indicator = "chatPerforming", -- deadCanChat = true -- }) -- @see ChatClassStructure function ix.chat.Register(chatType, data) chatType = string.lower(chatType) if (!data.CanHear) then -- Have a substitute if the canHear property is not found. function data:CanHear(speaker, listener) -- The speaker will be heard by everyone. return true end elseif (isnumber(data.CanHear)) then -- Use the value as a range and create a function to compare distances. local range = data.CanHear * data.CanHear data.range = range function data:CanHear(speaker, listener) -- Length2DSqr is faster than Length2D, so just check the squares. return (speaker:GetPos() - listener:GetPos()):LengthSqr() <= self.range end end -- Allow players to use this chat type by default. if (!data.CanSay) then function data:CanSay(speaker, text) if (!self.deadCanChat and !speaker:Alive()) then speaker:NotifyLocalized("noPerm") return false end return true end end -- Chat text color. data.color = data.color or Color(242, 230, 160) if (!data.OnChatAdd) then data.format = data.format or "%s: \"%s\"" function data:OnChatAdd(speaker, text, anonymous, info) local color = self.color local name = anonymous and L"someone" or hook.Run("GetCharacterName", speaker, chatType) or (IsValid(speaker) and speaker:Name() or "Console") if (self.GetColor) then color = self:GetColor(speaker, text, info) end local translated = L2(chatType.."Format", name, text) chat.AddText(color, translated or string.format(self.format, name, text)) end end if (CLIENT and data.prefix) then if (istable(data.prefix)) then for _, v in ipairs(data.prefix) do if (v:utf8sub(1, 1) == "/") then ix.command.Add(v:utf8sub(2), { description = data.description, arguments = ix.type.text, indicator = data.indicator, bNoIndicator = data.bNoIndicator, chatClass = data, OnCheckAccess = function() return true end, OnRun = function(self, client, message) end }) end end else ix.command.Add(isstring(data.prefix) and data.prefix:utf8sub(2) or chatType, { description = data.description, arguments = ix.type.text, indicator = data.indicator, bNoIndicator = data.bNoIndicator, chatClass = data, OnCheckAccess = function() return true end, OnRun = function(self, client, message) end }) end end data.uniqueID = chatType ix.chat.classes[chatType] = data end --- Identifies which chat mode should be used. -- @realm shared -- @player client Player who is speaking -- @string message Message to parse -- @bool[opt=false] bNoSend Whether or not to send the chat message after parsing -- @treturn string Name of the chat type -- @treturn string Message that was parsed -- @treturn bool Whether or not the speaker should be anonymous function ix.chat.Parse(client, message, bNoSend) local anonymous = false local chatType = "ic" -- Loop through all chat classes and see if the message contains their prefix. for k, v in pairs(ix.chat.classes) do local isChosen = false local chosenPrefix = "" local noSpaceAfter = v.noSpaceAfter -- Check through all prefixes if the chat type has more than one. if (istable(v.prefix)) then for _, prefix in ipairs(v.prefix) do prefix = prefix:utf8lower() local fullPrefix = prefix .. (noSpaceAfter and "" or " ") -- Checking if the start of the message has the prefix. if (message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()) then isChosen = true chosenPrefix = fullPrefix break end end -- Otherwise the prefix itself is checked. elseif (isstring(v.prefix)) then local prefix = v.prefix:utf8lower() local fullPrefix = prefix .. (noSpaceAfter and "" or " ") isChosen = message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower() chosenPrefix = fullPrefix end -- If the checks say we have the proper chat type, then the chat type is the chosen one! -- If this is not chosen, the loop continues. If the loop doesn't find the correct chat -- type, then it falls back to IC chat as seen by the chatType variable above. if (isChosen) then -- Set the chat type to the chosen one. chatType = k -- Remove the prefix from the chat type so it does not show in the message. message = message:utf8sub(chosenPrefix:utf8len() + 1) if (ix.chat.classes[k].noSpaceAfter and message:utf8sub(1, 1):match("%s")) then message = message:utf8sub(2) end break end end if (!message:find("%S")) then return end -- Only send if needed. if (SERVER and !bNoSend) then -- Send the correct chat type out so other player see the message. ix.chat.Send(client, chatType, hook.Run("PlayerMessageSend", client, chatType, message, anonymous) or message, anonymous) end -- Return the chosen chat type and the message that was sent if needed for some reason. -- This would be useful if you want to send the message on your own. return chatType, message, anonymous end --- Formats a string to fix basic grammar - removing extra spacing at the beginning and end, capitalizing the first character, -- and making sure it ends in punctuation. -- @realm shared -- @string text String to format -- @treturn string Formatted string -- @usage print(ix.chat.Format("hello")) -- > Hello. -- @usage print(ix.chat.Format("wow!")) -- > Wow! function ix.chat.Format(text) text = string.Trim(text) local last = text:utf8sub(-1) if (last != "." and last != "?" and last != "!" and last != "-" and last != "\"") then text = text .. "." end return text:utf8sub(1, 1):utf8upper() .. text:utf8sub(2) end if (SERVER) then util.AddNetworkString("ixChatMessage") --- Send a chat message using the specified chat type. -- @realm server -- @player speaker Player who is speaking -- @string chatType Name of the chat type -- @string text Message to send -- @bool[opt=false] bAnonymous Whether or not the speaker should be anonymous -- @tab[opt=nil] receivers The players to replicate send the message to -- @tab[opt=nil] data Additional data for this chat message function ix.chat.Send(speaker, chatType, text, bAnonymous, receivers, data) if (!chatType) then return end data = data or {} chatType = string.lower(chatType) if (IsValid(speaker) and hook.Run("PrePlayerMessageSend", speaker, chatType, text, bAnonymous) == false) then return end local class = ix.chat.classes[chatType] if (class and class:CanSay(speaker, text, data) != false) then if (class.CanHear and !receivers) then receivers = {} for _, v in player.Iterator() do if (v:GetCharacter() and class:CanHear(speaker, v, data) != false) then receivers[#receivers + 1] = v end end if (#receivers == 0) then return end end -- Format the message if needed before we run the hook. local rawText = text local maxLength = ix.config.Get("chatMax") -- Trim the text and remove extra spaces. text = string.gsub(text, "%s+", " ") if (text:utf8len() > maxLength) then text = text:utf8sub(0, maxLength) end if (ix.config.Get("chatAutoFormat") and hook.Run("CanAutoFormatMessage", speaker, chatType, text)) then text = ix.chat.Format(text) end text = hook.Run("PlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, rawText) or text net.Start("ixChatMessage") net.WriteEntity(speaker) net.WriteString(chatType) net.WriteString(text) net.WriteBool(bAnonymous or false) net.WriteTable(data or {}) net.Send(receivers) return text end end else function ix.chat.Send(speaker, chatType, text, anonymous, data) local class = ix.chat.classes[chatType] if (class) then -- Trim the text and remove extra spaces. text = string.gsub(text, "%s+", " ") -- luacheck: globals CHAT_CLASS CHAT_CLASS = class class:OnChatAdd(speaker, text, anonymous, data) CHAT_CLASS = nil end end -- Call OnChatAdd for the appropriate chatType. net.Receive("ixChatMessage", function() local client = net.ReadEntity() local chatType = net.ReadString() local text = net.ReadString() local anonymous = net.ReadBool() local data = net.ReadTable() if (IsValid(client)) then local info = { chatType = chatType, text = text, anonymous = anonymous, data = data } hook.Run("MessageReceived", client, info) ix.chat.Send(client, info.chatType or chatType, info.text or text, info.anonymous or anonymous, info.data) else ix.chat.Send(nil, chatType, text, anonymous, data) end end) end -- Add the default chat types here. do -- Load the chat types after the configs so we can access changed configs. hook.Add("InitializedConfig", "ixChatTypes", function() -- The default in-character chat. ix.chat.Register("ic", { format = "%s says \"%s\"", indicator = "chatTalking", GetColor = function(self, speaker, text) -- If you are looking at the speaker, make it greener to easier identify who is talking. if (LocalPlayer():GetEyeTrace().Entity == speaker) then return ix.config.Get("chatListenColor") end -- Otherwise, use the normal chat color. return ix.config.Get("chatColor") end, CanHear = ix.config.Get("chatRange", 280) }) -- Actions and such. ix.chat.Register("me", { format = "** %s %s", GetColor = ix.chat.classes.ic.GetColor, CanHear = ix.config.Get("chatRange", 280) * 2, prefix = {"/Me", "/Action"}, description = "@cmdMe", indicator = "chatPerforming", deadCanChat = true }) -- Actions and such. ix.chat.Register("it", { OnChatAdd = function(self, speaker, text) chat.AddText(ix.config.Get("chatColor"), "** "..text) end, CanHear = ix.config.Get("chatRange", 280) * 2, prefix = {"/It"}, description = "@cmdIt", indicator = "chatPerforming", deadCanChat = true }) -- Whisper chat. ix.chat.Register("w", { format = "%s whispers \"%s\"", GetColor = function(self, speaker, text) local color = ix.chat.classes.ic:GetColor(speaker, text) -- Make the whisper chat slightly darker than IC chat. return Color(color.r - 35, color.g - 35, color.b - 35) end, CanHear = ix.config.Get("chatRange", 280) * 0.25, prefix = {"/W", "/Whisper"}, description = "@cmdW", indicator = "chatWhispering" }) -- Yelling out loud. ix.chat.Register("y", { format = "%s yells \"%s\"", GetColor = function(self, speaker, text) local color = ix.chat.classes.ic:GetColor(speaker, text) -- Make the yell chat slightly brighter than IC chat. return Color(color.r + 35, color.g + 35, color.b + 35) end, CanHear = ix.config.Get("chatRange", 280) * 2, prefix = {"/Y", "/Yell"}, description = "@cmdY", indicator = "chatYelling" }) -- Out of character. ix.chat.Register("ooc", { CanSay = function(self, speaker, text) if (!ix.config.Get("allowGlobalOOC")) then speaker:NotifyLocalized("Global OOC is disabled on this server.") return false else local delay = ix.config.Get("oocDelay", 10) -- Only need to check the time if they have spoken in OOC chat before. if (delay > 0 and speaker.ixLastOOC) then local lastOOC = CurTime() - speaker.ixLastOOC -- Use this method of checking time in case the oocDelay config changes. if (lastOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then speaker:NotifyLocalized("oocDelay", delay - math.ceil(lastOOC)) return false end end -- Save the last time they spoke in OOC. speaker.ixLastOOC = CurTime() end end, OnChatAdd = function(self, speaker, text) -- @todo remove and fix actual cause of speaker being nil if (!IsValid(speaker)) then return end local icon = "icon16/user.png" if (speaker:IsSuperAdmin()) then icon = "icon16/shield.png" elseif (speaker:IsAdmin()) then icon = "icon16/star.png" elseif (speaker:IsUserGroup("moderator") or speaker:IsUserGroup("operator")) then icon = "icon16/wrench.png" elseif (speaker:IsUserGroup("vip") or speaker:IsUserGroup("donator") or speaker:IsUserGroup("donor")) then icon = "icon16/heart.png" end icon = Material(hook.Run("GetPlayerIcon", speaker) or icon) chat.AddText(icon, Color(255, 50, 50), "[OOC] ", speaker, color_white, ": "..text) end, prefix = {"//", "/OOC"}, description = "@cmdOOC", noSpaceAfter = true }) -- Local out of character. ix.chat.Register("looc", { CanSay = function(self, speaker, text) local delay = ix.config.Get("loocDelay", 0) -- Only need to check the time if they have spoken in OOC chat before. if (delay > 0 and speaker.ixLastLOOC) then local lastLOOC = CurTime() - speaker.ixLastLOOC -- Use this method of checking time in case the oocDelay config changes. if (lastLOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then speaker:NotifyLocalized("loocDelay", delay - math.ceil(lastLOOC)) return false end end -- Save the last time they spoke in OOC. speaker.ixLastLOOC = CurTime() end, OnChatAdd = function(self, speaker, text) chat.AddText(Color(255, 50, 50), "[LOOC] ", ix.config.Get("chatColor"), speaker:Name()..": "..text) end, CanHear = ix.config.Get("chatRange", 280), prefix = {".//", "[[", "/LOOC"}, description = "@cmdLOOC", noSpaceAfter = true }) -- Roll information in chat. ix.chat.Register("roll", { format = "** %s has rolled %s out of %s.", color = Color(155, 111, 176), CanHear = ix.config.Get("chatRange", 280), deadCanChat = true, OnChatAdd = function(self, speaker, text, bAnonymous, data) local max = data.max or 100 local translated = L2(self.uniqueID.."Format", speaker:Name(), text, max) chat.AddText(self.color, translated and "** "..translated or string.format(self.format, speaker:Name(), text, max )) end }) -- run a hook after we add the basic chat classes so schemas/plugins can access their info as soon as possible if needed hook.Run("InitializedChatClasses") end) end -- Private messages between players. ix.chat.Register("pm", { format = "[PM] %s -> %s: %s", color = Color(125, 150, 75, 255), deadCanChat = true, OnChatAdd = function(self, speaker, text, bAnonymous, data) chat.AddText(self.color, string.format(self.format, speaker:GetName(), data.target:GetName(), text)) if (LocalPlayer() != speaker) then surface.PlaySound("hl1/fvox/bell.wav") end end }) -- Global events. ix.chat.Register("event", { CanHear = 1000000, OnChatAdd = function(self, speaker, text) chat.AddText(Color(255, 150, 0), text) end, indicator = "chatPerforming" }) ix.chat.Register("connect", { CanSay = function(self, speaker, text) return !IsValid(speaker) end, OnChatAdd = function(self, speaker, text) local icon = ix.util.GetMaterial("icon16/user_add.png") chat.AddText(icon, Color(150, 150, 200), L("playerConnected", text)) end, noSpaceAfter = true }) ix.chat.Register("disconnect", { CanSay = function(self, speaker, text) return !IsValid(speaker) end, OnChatAdd = function(self, speaker, text) local icon = ix.util.GetMaterial("icon16/user_delete.png") chat.AddText(icon, Color(200, 150, 200), L("playerDisconnected", text)) end, noSpaceAfter = true }) ix.chat.Register("notice", { CanSay = function(self, speaker, text) return !IsValid(speaker) end, OnChatAdd = function(self, speaker, text, bAnonymous, data) local icon = ix.util.GetMaterial(data.bError and "icon16/comment_delete.png" or "icon16/comment.png") chat.AddText(icon, data.bError and Color(200, 175, 200, 255) or Color(175, 200, 255), text) end, noSpaceAfter = true }) -- Why does ULX even have a /me command? hook.Remove("PlayerSay", "ULXMeCheck") ================================================ FILE: gamemode/core/libs/sh_class.lua ================================================ --[[-- Helper library for loading/getting class information. Classes are temporary assignments for characters - analogous to a "job" in a faction. For example, you may have a police faction in your schema, and have "police recruit" and "police chief" as different classes in your faction. Anyone can join a class in their faction by default, but you can restrict this as you need with `CLASS.CanSwitchTo`. ]] -- @module ix.class --- Class definition table returned by `ix.class.Get`. -- @realm shared -- @tab class Class definition table. -- Default keys: -- -- - `index` (number) - numeric class ID -- - `uniqueID` (string) - stable identifier (e.g. `citizen`) -- - `name` (string) -- - `description` (string) -- - `faction` (number) - Faction ID this class belongs to -- - `isDefault` (boolean) - whether it is the default class for the faction -- - `limit` (number) - maximum number of players allowed in this class (0 for unlimited) -- - `CanSwitchTo` (function|nil) - optional callback to check if a player may switch if (SERVER) then util.AddNetworkString("ixClassUpdate") end ix.class = ix.class or {} ix.class.list = {} local charMeta = ix.meta.character --- Loads classes from a directory. -- @realm shared -- @internal -- @string directory The path to the class files. function ix.class.LoadFromDir(directory) for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do -- Get the name without the "sh_" prefix and ".lua" suffix. local niceName = v:sub(4, -5) -- Determine a numeric identifier for this class. local index = #ix.class.list + 1 local halt for _, v2 in ipairs(ix.class.list) do if (v2.uniqueID == niceName) then halt = true break end end if (halt == true) then continue end -- Set up a global table so the file has access to the class table. CLASS = {index = index, uniqueID = niceName} CLASS.name = "Unknown" CLASS.description = "No description available." CLASS.limit = 0 -- For future use with plugins. if (PLUGIN) then CLASS.plugin = PLUGIN.uniqueID end ix.util.Include(directory.."/"..v, "shared") -- Why have a class without a faction? if (!CLASS.faction or !team.Valid(CLASS.faction)) then ErrorNoHalt("Class '"..niceName.."' does not have a valid faction!\n") CLASS = nil continue end -- Allow classes to be joinable by default. if (!CLASS.CanSwitchTo) then CLASS.CanSwitchTo = function(client) return true end end ix.class.list[index] = CLASS CLASS = nil end end --- Determines if a player is allowed to join a specific class. -- @realm shared -- @player client Player to check -- @number class Index of the class -- @treturn bool Whether or not the player can switch to the class. -- @treturn string The reason why the player cannot switch (if applicable). -- @usage -- Check if a player can join class ID 2. -- -- For our example, they can't- because they are in the wrong faction. -- local canJoin, reason = ix.class.CanSwitchTo(player, 2) -- if (!canJoin) then -- print("Player cannot join class: "..reason) -- end -- > Player cannot join class: not correct team function ix.class.CanSwitchTo(client, class) -- Get the class table by its numeric identifier. local info = ix.class.list[class] -- See if the class exists. if (!info) then return false, "no info" end -- If the player's faction matches the class's faction. if (client:Team() != info.faction) then return false, "not correct team" end if (client:GetCharacter():GetClass() == class) then return false, "same class request" end if (info.limit > 0) then if (#ix.class.GetPlayers(info.index) >= info.limit) then return false, "class is full" end end if (hook.Run("CanPlayerJoinClass", client, class, info) == false) then return false end -- See if the class allows the player to join it. return info:CanSwitchTo(client) end --- Gets a class definition table by numeric identifier. -- @realm shared -- @tparam number identifier Numeric class identifier. -- @treturn table|nil Class definition table (see `class` table docs). -- @usage -- Print the name of class ID 1 -- print(ix.class.Get(1).name) -- > Citizen function ix.class.Get(identifier) return ix.class.list[identifier] end --- Retrieves all players currently assigned to a specific class. -- @realm shared -- @number class Index of the class -- @treturn table Numerically indexed table of players in the class, or an empty table if none are found. -- @usage -- Print all players in class ID 1 -- for _, ply in ipairs(ix.class.GetPlayers(1)) do -- print(ply:GetName()) -- end -- > Player1 -- > Player2 -- > etc function ix.class.GetPlayers(class) local players = {} for _, v in player.Iterator() do local char = v:GetCharacter() if (char and char:GetClass() == class) then table.insert(players, v) end end return players end if (SERVER) then --- Character class methods -- @classmod Character --- Makes this character join a class. This automatically calls `KickClass` for you. -- @realm server -- @number class Index of the class to join -- @treturn bool Whether or not the character has successfully joined the class function charMeta:JoinClass(class) if (!class) then self:KickClass() return false end local oldClass = self:GetClass() local client = self:GetPlayer() if (ix.class.CanSwitchTo(client, class)) then self:SetClass(class) hook.Run("PlayerJoinedClass", client, class, oldClass) return true end return false end --- Kicks this character out of the class they are currently in. -- @realm server function charMeta:KickClass() local client = self:GetPlayer() if (!client) then return end local goClass for k, v in pairs(ix.class.list) do if (v.faction == client:Team() and v.isDefault) then goClass = k break end end if (!goClass) then ErrorNoHaltWithStack("[Helix] No default class set for faction '" .. team.GetName(client:Team()) .. "'") return end self:JoinClass(goClass) hook.Run("PlayerJoinedClass", client, goClass) end function GM:PlayerJoinedClass(client, class, oldClass) local info = ix.class.list[class] local info2 = ix.class.list[oldClass] if (info.OnSet) then info:OnSet(client) end if (info2 and info2.OnLeave) then info2:OnLeave(client) end net.Start("ixClassUpdate") net.WriteEntity(client) net.Broadcast() end end ================================================ FILE: gamemode/core/libs/sh_command.lua ================================================ --[[-- Registration, parsing, and handling of commands. Commands can be ran through the chat with slash commands or they can be executed through the console. Commands can be manually restricted to certain usergroups using a [CAMI](https://github.com/glua/CAMI)-compliant admin mod. ]] -- @module ix.command --- When registering commands with `ix.command.Add`, you'll need to pass in a valid command structure. This is simply a table -- with various fields defined to describe the functionality of the command. -- @realm shared -- @table CommandStructure -- @field[type=function] OnRun This function is called when the command has passed all the checks and can execute. The first two -- arguments will be the running command table and the calling player. If the arguments field has been specified, the arguments -- will be passed as regular function parameters rather than in a table. -- -- When the arguments field is defined: `OnRun(self, client, target, length, message)` -- -- When the arguments field is NOT defined: `OnRun(self, client, arguments)` -- @field[type=string,opt="@noDesc"] description The help text that appears when the user types in the command. If the string is -- prefixed with `"@"`, it will use a language phrase. -- @field[type=table,opt=nil] argumentNames An array of strings corresponding to each argument of the command. This ignores the -- name that's specified in the `OnRun` function arguments and allows you to use any string to change the text that displays -- in the command's syntax help. When using this field, make sure that the amount is equal to the amount of arguments, as such: -- COMMAND.arguments = {ix.type.character, ix.type.number} -- COMMAND.argumentNames = {"target char", "cash (1-1000)"} -- @field[type=table,opt] arguments If this field is defined, then additional checks will be performed to ensure that the -- arguments given to the command are valid. This removes extra boilerplate code since all the passed arguments are guaranteed -- to be valid. See `CommandArgumentsStructure` for more information. -- @field[type=boolean,opt=false] adminOnly Provides an additional check to see if the user is an admin before running. -- @field[type=boolean,opt=false] superAdminOnly Provides an additional check to see if the user is a superadmin before running. -- @field[type=string,opt=nil] privilege Manually specify a privilege name for this command. It will always be prefixed with -- `"Helix - "`. This is used in the case that you want to group commands under the same privilege, or use a privilege that -- you've already defined (i.e grouping `/CharBan` and `/CharUnban` into the `Helix - Ban Character` privilege). -- @field[type=function,opt=nil] OnCheckAccess This callback checks whether or not the player is allowed to run the command. -- This callback should **NOT** be used in conjunction with `adminOnly` or `superAdminOnly`, as populating those -- fields create a custom a `OnCheckAccess` callback for you internally. This is used in cases where you want more fine-grained -- access control for your command. -- -- Keep in mind that this is a **SHARED** callback; the command will not show up the client if the callback returns `false`. --- Rather than checking the validity for arguments in your command's `OnRun` function, you can have Helix do it for you to -- reduce the amount of boilerplate code that needs to be written. This can be done by populating the `arguments` field. -- -- When using the `arguments` field in your command, you are specifying specific types that you expect to receive when the -- command is ran successfully. This means that before `OnRun` is called, the arguments passed to the command from a user will -- be verified to be valid. Each argument is an `ix.type` entry that specifies the expected type for that argument. Optional -- arguments can be specified by using a bitwise OR with the special `ix.type.optional` type. When specified as optional, the -- argument can be `nil` if the user has not entered anything for that argument - otherwise it will be valid. -- -- Note that optional arguments must always be at the end of a list of arguments - or rather, they must not follow a required -- argument. The `syntax` field will be automatically populated when using strict arguments, which means you shouldn't fill out -- the `syntax` field yourself. The arguments you specify will have the same names as the arguments in your OnRun function. -- -- Consider this example command: -- ix.command.Add("CharSlap", { -- description = "Slaps a character with a large trout.", -- adminOnly = true, -- arguments = { -- ix.type.character, -- bit.bor(ix.type.number, ix.type.optional) -- }, -- OnRun = function(self, client, target, damage) -- -- WHAM! -- end -- }) -- Here, we've specified the first argument called `target` to be of type `character`, and the second argument called `damage` -- to be of type `number`. The `damage` argument is optional, meaning that the command will still run if the user has not -- specified any value for the damage. In this case, we'll need to check if it was specified by doing a simple -- `if (damage) then`. The syntax field will be automatically populated with the value `" [damage: number]"`. -- @realm shared -- @table CommandArgumentsStructure ix.command = ix.command or {} ix.command.list = ix.command.list or {} local COMMAND_PREFIX = "/" local function ArgumentCheckStub(command, client, given) local arguments = command.arguments local result = {} for i = 1, #arguments do local bOptional = bit.band(arguments[i], ix.type.optional) == ix.type.optional local argType = bOptional and bit.bxor(arguments[i], ix.type.optional) or arguments[i] local argument = given[i] if (!argument and !bOptional) then return L("invalidArg", client, i) end if (argType == ix.type.string) then if (!argument and bOptional) then result[#result + 1] = nil else result[#result + 1] = tostring(argument) end elseif (argType == ix.type.text) then result[#result + 1] = table.concat(given, " ", i) or "" break elseif (argType == ix.type.number) then local value = tonumber(argument) if (!bOptional and !value) then return L("invalidArg", client, i) end result[#result + 1] = value elseif (argType == ix.type.player or argType == ix.type.character) then local bPlayer = argType == ix.type.player local value = ix.util.FindPlayer(argument or "") -- argument could be nil due to optional type -- FindPlayer emits feedback for us if (!value and !bOptional) then return L(bPlayer and "plyNoExist" or "charNoExist", client) end -- check for the character if we're using the character type if (!bPlayer) then local character = value:GetCharacter() if (!character) then return L("charNoExist", client) end value = character end result[#result + 1] = value elseif (argType == ix.type.steamid) then local value = argument:match("STEAM_(%d+):(%d+):(%d+)") if (!value and bOptional) then return L("invalidArg", client, i) end result[#result + 1] = value elseif (argType == ix.type.bool) then if (argument == nil and bOptional) then result[#result + 1] = nil else result[#result + 1] = tobool(argument) end end end return result end --- Creates a new command. -- @realm shared -- @string command Name of the command (recommended in UpperCamelCase) -- @tparam CommandStructure data Data describing the command -- @see CommandStructure -- @see CommandArgumentsStructure function ix.command.Add(command, data) data.name = string.gsub(command, "%s", "") data.description = data.description or "" command = command:lower() data.uniqueID = command -- Why bother adding a command if it doesn't do anything. if (!data.OnRun) then return ErrorNoHalt("Command '"..command.."' does not have a callback, not adding!\n") end -- Add a function to get the description that can be overridden. if (!data.GetDescription) then -- Check if the description is using a language string. if (data.description:sub(1, 1) == "@") then function data:GetDescription() return L(self.description:sub(2)) end else -- Otherwise just return the raw description. function data:GetDescription() return self.description end end end -- OnCheckAccess by default will rely on CAMI for access information with adminOnly/superAdminOnly being fallbacks if (!data.OnCheckAccess) then if (data.group) then ErrorNoHalt("Command '" .. data.name .. "' tried to use the deprecated field 'group'!\n") return end local privilege = "Helix - " .. (isstring(data.privilege) and data.privilege or data.name) -- we could be using a previously-defined privilege if (!CAMI.GetPrivilege(privilege)) then CAMI.RegisterPrivilege({ Name = privilege, MinAccess = data.superAdminOnly and "superadmin" or (data.adminOnly and "admin" or "user"), Description = data.description }) end function data:OnCheckAccess(client) local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil) return bHasAccess end end -- if we have an arguments table, then we're using the new command format if (data.arguments) then local bFirst = true local bLastOptional = false local bHasArgumentNames = istable(data.argumentNames) data.syntax = "" -- @todo deprecate this in favour of argumentNames data.argumentNames = bHasArgumentNames and data.argumentNames or {} -- if one argument is supplied by itself, put it into a table if (!istable(data.arguments)) then data.arguments = {data.arguments} end if (bHasArgumentNames and #data.argumentNames != #data.arguments) then return ErrorNoHalt(string.format( "Command '%s' doesn't have argument names that correspond to each argument\n", command )) end -- check the arguments table to see if its entries are valid for i = 1, #data.arguments do local argument = data.arguments[i] local argumentName = debug.getlocal(data.OnRun, 2 + i) if (argument == ix.type.optional) then return ErrorNoHalt(string.format( "Command '%s' tried to use an optional argument for #%d without specifying type\n", command, i )) elseif (!isnumber(argument)) then return ErrorNoHalt(string.format( "Command '%s' tried to use an invalid type for argument #%d\n", command, i )) elseif (argument == ix.type.array or bit.band(argument, ix.type.array) > 0) then return ErrorNoHalt(string.format( "Command '%s' tried to use an unsupported type 'array' for argument #%d\n", command, i )) end local bOptional = bit.band(argument, ix.type.optional) > 0 argument = bOptional and bit.bxor(argument, ix.type.optional) or argument if (!ix.type[argument]) then return ErrorNoHalt(string.format( "Command '%s' tried to use an invalid type for argument #%d\n", command, i )) elseif (!isstring(argumentName)) then return ErrorNoHalt(string.format( "Command '%s' is missing function argument for command argument #%d\n", command, i )) elseif (argument == ix.type.text and i != #data.arguments) then return ErrorNoHalt(string.format( "Command '%s' tried to use a text argument outside of the last argument\n", command )) elseif (!bOptional and bLastOptional) then return ErrorNoHalt(string.format( "Command '%s' tried to use an required argument after an optional one\n", command )) end -- text is always optional and will return an empty string if nothing is specified, rather than nil if (argument == ix.type.text) then data.arguments[i] = bit.bor(ix.type.text, ix.type.optional) bOptional = true end if (!bHasArgumentNames) then data.argumentNames[i] = argumentName end data.syntax = data.syntax .. (bFirst and "" or " ") .. string.format((bOptional and "[%s: %s]" or "<%s: %s>"), argumentName, ix.type[argument]) bFirst = false bLastOptional = bOptional end if (data.syntax:utf8len() == 0) then data.syntax = "" end else data.syntax = data.syntax or "" end -- Add the command to the list of commands. local alias = data.alias if (alias) then if (istable(alias)) then for _, v in ipairs(alias) do ix.command.list[v:lower()] = data end elseif (isstring(alias)) then ix.command.list[alias:lower()] = data end end ix.command.list[command] = data end --- Returns true if a player is allowed to run a certain command. -- @realm shared -- @player client Player to check access for -- @string command Name of the command to check access for -- @treturn bool Whether or not the player is allowed to run the command function ix.command.HasAccess(client, command) command = ix.command.list[command:lower()] if (command) then if (command.OnCheckAccess) then return command:OnCheckAccess(client) else return true end end return false end --- Returns a table of arguments from a given string. -- Words separated by spaces will be considered one argument. To have an argument containing multiple words, they must be -- contained within quotation marks. -- @realm shared -- @string text String to extract arguments from -- @treturn table Arguments extracted from string -- @usage PrintTable(ix.command.ExtractArgs("these are \"some arguments\"")) -- > 1 = these -- > 2 = are -- > 3 = some arguments function ix.command.ExtractArgs(text) local skip = 0 local arguments = {} local curString = "" for i = 1, text:utf8len() do if (i <= skip) then continue end local c = text:utf8sub(i, i) if (c == "\"") then local match = text:utf8sub(i):match("%b\"\"") if (match) then curString = "" skip = i + match:utf8len() arguments[#arguments + 1] = match:utf8sub(2, -2) else curString = curString..c end elseif (c == " " and curString != "") then arguments[#arguments + 1] = curString curString = "" else if (c == " " and curString == "") then continue end curString = curString..c end end if (curString != "") then arguments[#arguments + 1] = curString end return arguments end --- Returns an array of potential commands by unique id. -- When bSorted is true, the commands will be sorted by name. When bReorganize is true, it will move any exact match to the top -- of the array. When bRemoveDupes is true, it will remove any commands that have the same NAME. -- @realm shared -- @string identifier Search query -- @bool[opt=false] bSorted Whether or not to sort the commands by name -- @bool[opt=false] bReorganize Whether or not any exact match will be moved to the top of the array -- @bool[opt=false] bRemoveDupes Whether or not to remove any commands that have the same name -- @treturn table Array of command tables whose name partially or completely matches the search query function ix.command.FindAll(identifier, bSorted, bReorganize, bRemoveDupes) local result = {} local iterator = bSorted and SortedPairs or pairs local fullMatch identifier = identifier:lower() if (identifier == "/") then -- we don't simply copy because we need numeric indices for _, v in iterator(ix.command.list) do result[#result + 1] = v end return result elseif (identifier:utf8sub(1, 1) == "/") then identifier = identifier:utf8sub(2) end for k, v in iterator(ix.command.list) do if (k:match(identifier)) then local index = #result + 1 result[index] = v if (k == identifier) then fullMatch = index end end end if (bReorganize and fullMatch and fullMatch != 1) then result[1], result[fullMatch] = result[fullMatch], result[1] end if (bRemoveDupes) then local commandNames = {} -- using pairs intead of ipairs because we might remove from array for k, v in pairs(result) do if (commandNames[v.name]) then table.remove(result, k) end commandNames[v.name] = true end end return result end if (SERVER) then util.AddNetworkString("ixCommand") --- Attempts to find a player by an identifier. If unsuccessful, a notice will be displayed to the specified player. The -- search criteria is derived from `ix.util.FindPlayer`. -- @realm server -- @player client Player to give a notification to if the player could not be found -- @string name Search query -- @treturn[1] player Player that matches the given search query -- @treturn[2] nil If a player could not be found -- @see ix.util.FindPlayer function ix.command.FindPlayer(client, name) local target = isstring(name) and ix.util.FindPlayer(name) or NULL if (IsValid(target)) then return target else client:NotifyLocalized("plyNoExist") end end --- Forces a player to execute a command by name. -- @realm server -- @player client Player who is executing the command -- @string command Full name of the command to be executed. This string gets lowered, but it's good practice to stick with -- the exact name of the command -- @tab arguments Array of arguments to be passed to the command -- @usage ix.command.Run(player.GetByID(1), "Roll", {10}) function ix.command.Run(client, command, arguments) if ((client.ixCommandCooldown or 0) > RealTime()) then return end command = ix.command.list[tostring(command):lower()] if (!command) then return end -- we throw it into a table since arguments get unpacked and only -- the arguments table gets passed in by default local argumentsTable = arguments arguments = {argumentsTable} -- if feedback is non-nil, we can assume that the command failed -- and is a phrase string local feedback -- check for group access if (command.OnCheckAccess) then local bSuccess, phrase = command:OnCheckAccess(client) feedback = !bSuccess and L(phrase and phrase or "noPerm", client) or nil end -- check for strict arguments if (!feedback and command.arguments) then arguments = ArgumentCheckStub(command, client, argumentsTable) if (isstring(arguments)) then feedback = arguments end end -- run the command if all the checks passed if (!feedback) then local results = {command:OnRun(client, unpack(arguments))} local phrase = results[1] -- check to see if the command has returned a phrase string and display it if (isstring(phrase)) then if (IsValid(client)) then if (phrase:sub(1, 1) == "@") then client:NotifyLocalized(phrase:sub(2), unpack(results, 2)) else client:Notify(phrase) end else -- print message since we're running from the server console print(phrase) end end client.ixCommandCooldown = RealTime() + 0.5 if (IsValid(client)) then ix.log.Add(client, "command", COMMAND_PREFIX .. command.name, argumentsTable and table.concat(argumentsTable, " ")) end else client:Notify(feedback) end end --- Parses a chat string and runs the command if one is found. Specifically, it checks for commands in a string with the -- format `/CommandName some arguments` -- @realm server -- @player client Player who is executing the command -- @string text Input string to search for the command format -- @string[opt] realCommand Specific command to check for. If this is specified, it will not try to run any command that's -- found at the beginning - only if it matches `realCommand` -- @tab[opt] arguments Array of arguments to pass to the command. If not specified, it will try to extract it from the -- string specified in `text` using `ix.command.ExtractArgs` -- @treturn bool Whether or not a command has been found -- @usage ix.command.Parse(player.GetByID(1), "/roll 10") function ix.command.Parse(client, text, realCommand, arguments) if (realCommand or text:utf8sub(1, 1) == COMMAND_PREFIX) then -- See if the string contains a command. local match = realCommand or text:utf8lower():match(COMMAND_PREFIX.."([_%w]+)") -- is it unicode text? if (!match) then local post = string.Explode(" ", text) local len = string.len(post[1]) match = post[1]:utf8sub(2, len) end match = match:utf8lower() local command = ix.command.list[match] -- We have a valid, registered command. if (command) then -- Get the arguments like a console command. if (!arguments) then arguments = ix.command.ExtractArgs(text:utf8sub(match:utf8len() + 3)) end -- Runs the actual command. ix.command.Run(client, match, arguments) else if (IsValid(client)) then client:NotifyLocalized("cmdNoExist") else print("Sorry, that command does not exist.") end end return true end return false end concommand.Add("ix", function(client, _, arguments) local command = arguments[1] table.remove(arguments, 1) ix.command.Parse(client, nil, command or "", arguments) end) net.Receive("ixCommand", function(length, client) if ((client.ixNextCmd or 0) < CurTime()) then local command = net.ReadString() local indices = net.ReadUInt(4) local arguments = {} for _ = 1, indices do local value = net.ReadType() if (isstring(value) or isnumber(value)) then arguments[#arguments + 1] = tostring(value) end end ix.command.Parse(client, nil, command, arguments) client.ixNextCmd = CurTime() + 0.2 end end) else --- Request the server to run a command. This mimics similar functionality to the client typing `/CommandName` in the chatbox. -- @realm client -- @string command Unique ID of the command -- @param ... Arguments to pass to the command -- @usage ix.command.Send("roll", 10) function ix.command.Send(command, ...) local arguments = {...} net.Start("ixCommand") net.WriteString(command) net.WriteUInt(#arguments, 4) for _, v in ipairs(arguments) do net.WriteType(v) end net.SendToServer() end concommand.Add("ix", function(client, _, arguments) ix.command.Send(table.remove(arguments, 1), unpack(arguments)) end, function(_, arguments) arguments = arguments:TrimLeft() local autocomplete = {} local command = string.Explode(" ", arguments)[1] for _, v in pairs(ix.command.FindAll(command, true, true)) do if (v.OnCheckAccess and !v:OnCheckAccess(LocalPlayer())) then continue end if (arguments:find(v.uniqueID, 1, true) == 1) then return { "ix " .. arguments, v:GetDescription(), L("syntax", v.syntax) } end table.insert(autocomplete, "ix " .. v.uniqueID) end return autocomplete end) end ================================================ FILE: gamemode/core/libs/sh_currency.lua ================================================ --- A library representing the server's currency system. -- @module ix.currency ix.currency = ix.currency or {} ix.currency.symbol = ix.currency.symbol or "$" ix.currency.singular = ix.currency.singular or "dollar" ix.currency.plural = ix.currency.plural or "dollars" ix.currency.model = ix.currency.model or "models/props_lab/box01a.mdl" --- Sets the currency type. -- @realm shared -- @string symbol The symbol of the currency. -- @string singular The name of the currency in it's singular form. -- @string plural The name of the currency in it's plural form. -- @string model The model of the currency entity. function ix.currency.Set(symbol, singular, plural, model) ix.currency.symbol = symbol ix.currency.singular = singular ix.currency.plural = plural ix.currency.model = model end --- Returns a formatted string according to the current currency. -- @realm shared -- @number amount The amount of cash being formatted. -- @treturn string The formatted string. function ix.currency.Get(amount) if (amount == 1) then return ix.currency.symbol.."1 "..ix.currency.singular else return ix.currency.symbol..amount.." "..ix.currency.plural end end --- Spawns an amount of cash at a specific location on the map. -- @realm shared -- @vector pos The position of the money to be spawned. -- @number amount The amount of cash being spawned. -- @angle[opt=angle_zero] angle The angle of the entity being spawned. -- @treturn entity The spawned money entity. function ix.currency.Spawn(pos, amount, angle) if (!amount or amount < 0) then print("[Helix] Can't create currency entity: Invalid Amount of money") return end local money = ents.Create("ix_money") money:Spawn() if (IsValid(pos) and pos:IsPlayer()) then pos = pos:GetItemDropPos(money) elseif (!isvector(pos)) then print("[Helix] Can't create currency entity: Invalid Position") money:Remove() return end money:SetPos(pos) -- double check for negative. money:SetAmount(math.Round(math.abs(amount))) money:SetAngles(angle or angle_zero) money:Activate() return money end function GM:OnPickupMoney(client, moneyEntity) if (IsValid(moneyEntity)) then local amount = moneyEntity:GetAmount() client:GetCharacter():GiveMoney(amount) end end do local character = ix.meta.character function character:HasMoney(amount) if (amount < 0) then print("Negative Money Check Received.") end return self:GetMoney() >= amount end function character:GiveMoney(amount, bNoLog) amount = math.abs(amount) if (!bNoLog) then ix.log.Add(self:GetPlayer(), "money", amount) end self:SetMoney(self:GetMoney() + amount) return true end function character:TakeMoney(amount, bNoLog) amount = math.abs(amount) if (!bNoLog) then ix.log.Add(self:GetPlayer(), "money", -amount) end self:SetMoney(self:GetMoney() - amount) return true end end ================================================ FILE: gamemode/core/libs/sh_date.lua ================================================ --[[-- Persistent date and time handling. All of Lua's time functions are dependent on the Unix epoch, which means we can't have dates that go further than 1970. This library remedies this problem. Time/date is represented by a `date` object that is queried, instead of relying on the seconds since the epoch. ## Futher documentation This library makes use of a third-party date library found at https://github.com/Tieske/date - you can find all documentation regarding the `date` object and its methods there. ]] -- @module ix.date ix.date = ix.date or {} ix.date.lib = ix.date.lib or include("thirdparty/sh_date.lua") ix.date.timeScale = ix.date.timeScale or ix.config.Get("secondsPerMinute", 60) -- seconds per minute ix.date.current = ix.date.current or ix.date.lib() -- current in-game date/time ix.date.start = ix.date.start or CurTime() -- arbitrary start time for calculating date/time offset if (SERVER) then util.AddNetworkString("ixDateSync") --- Loads the date from disk. -- @realm server -- @internal function ix.date.Initialize() local currentDate = ix.data.Get("date", nil, false, true) -- construct new starting date if we don't have it saved already if (!currentDate) then currentDate = { year = ix.config.Get("year"), month = ix.config.Get("month"), day = ix.config.Get("day"), hour = tonumber(os.date("%H")) or 0, min = tonumber(os.date("%M")) or 0, sec = tonumber(os.date("%S")) or 0 } currentDate = ix.date.lib.serialize(ix.date.lib(currentDate)) ix.data.Set("date", currentDate, false, true) end ix.date.timeScale = ix.config.Get("secondsPerMinute", 60) ix.date.current = ix.date.lib.construct(currentDate) end --- Updates the internal in-game date/time representation and resets the offset. -- @realm server -- @internal function ix.date.ResolveOffset() ix.date.current = ix.date.Get() ix.date.start = CurTime() end --- Updates the time scale of the in-game date/time. The time scale is given in seconds per minute (i.e how many real life -- seconds it takes for an in-game minute to pass). You should avoid using this function and use the in-game config menu to -- change the time scale instead. -- @realm server -- @internal -- @number secondsPerMinute New time scale function ix.date.UpdateTimescale(secondsPerMinute) ix.date.ResolveOffset() ix.date.timeScale = secondsPerMinute end --- Sends the current date to a player. This is done automatically when the player joins the server. -- @realm server -- @internal -- @player[opt=nil] client Player to send the date to, or `nil` to send to everyone function ix.date.Send(client) net.Start("ixDateSync") net.WriteFloat(ix.date.timeScale) net.WriteTable(ix.date.current) net.WriteFloat(ix.date.start) if (client) then net.Send(client) else net.Broadcast() end end --- Saves the current in-game date to disk. -- @realm server -- @internal function ix.date.Save() ix.date.bSaving = true ix.date.ResolveOffset() -- resolve offset so we save the actual time to disk ix.data.Set("date", ix.date.lib.serialize(ix.date.current), false, true) -- update config to reflect current saved date ix.config.Set("year", ix.date.current:getyear()) ix.config.Set("month", ix.date.current:getmonth()) ix.config.Set("day", ix.date.current:getday()) ix.date.bSaving = nil end else net.Receive("ixDateSync", function() local timeScale = net.ReadFloat() local currentDate = ix.date.lib.construct(net.ReadTable()) local startTime = net.ReadFloat() ix.date.timeScale = timeScale ix.date.current = currentDate ix.date.start = startTime end) end --- Returns the currently set date. -- @realm shared -- @treturn date Current in-game date function ix.date.Get() local minutesSinceStart = (CurTime() - ix.date.start) / ix.date.timeScale return ix.date.current:copy():addminutes(minutesSinceStart) end --- Returns a string formatted version of a date. -- @realm shared -- @string format Format string -- @date[opt=nil] currentDate Date to format. If nil, it will use the currently set date -- @treturn string Formatted date function ix.date.GetFormatted(format, currentDate) return (currentDate or ix.date.Get()):fmt(format) end --- Returns a serialized version of a date. This is useful when you need to network a date to clients, or save a date to disk. -- @realm shared -- @date[opt=nil] currentDate Date to serialize. If nil, it will use the currently set date -- @treturn table Serialized date function ix.date.GetSerialized(currentDate) return ix.date.lib.serialize(currentDate or ix.date.Get()) end --- Returns a date object from a table or serialized date. -- @realm shared -- @param currentDate Date to construct -- @treturn date Constructed date object function ix.date.Construct(currentDate) return ix.date.lib.construct(currentDate) end ================================================ FILE: gamemode/core/libs/sh_faction.lua ================================================ --- Helper library for loading/getting faction information. -- @module ix.faction ix.faction = ix.faction or {} ix.faction.teams = ix.faction.teams or {} ix.faction.indices = ix.faction.indices or {} local CITIZEN_MODELS = { "models/humans/group01/male_01.mdl", "models/humans/group01/male_02.mdl", "models/humans/group01/male_04.mdl", "models/humans/group01/male_05.mdl", "models/humans/group01/male_06.mdl", "models/humans/group01/male_07.mdl", "models/humans/group01/male_08.mdl", "models/humans/group01/male_09.mdl", "models/humans/group02/male_01.mdl", "models/humans/group02/male_03.mdl", "models/humans/group02/male_05.mdl", "models/humans/group02/male_07.mdl", "models/humans/group02/male_09.mdl", "models/humans/group01/female_01.mdl", "models/humans/group01/female_02.mdl", "models/humans/group01/female_03.mdl", "models/humans/group01/female_06.mdl", "models/humans/group01/female_07.mdl", "models/humans/group02/female_01.mdl", "models/humans/group02/female_03.mdl", "models/humans/group02/female_06.mdl", "models/humans/group01/female_04.mdl" } --- Loads factions from a directory. -- @realm shared -- @string directory The path to the factions files. function ix.faction.LoadFromDir(directory) for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do local niceName = v:sub(4, -5) FACTION = ix.faction.teams[niceName] or {index = table.Count(ix.faction.teams) + 1, isDefault = false} if (PLUGIN) then FACTION.plugin = PLUGIN.uniqueID end ix.util.Include(directory.."/"..v, "shared") if (!FACTION.name) then FACTION.name = "Unknown" ErrorNoHalt("Faction '"..niceName.."' is missing a name. You need to add a FACTION.name = \"Name\"\n") end if (!FACTION.color) then FACTION.color = Color(150, 150, 150) ErrorNoHalt("Faction '"..niceName.."' is missing a color. You need to add FACTION.color = Color(1, 2, 3)\n") end team.SetUp(FACTION.index, FACTION.name or "Unknown", FACTION.color or Color(125, 125, 125)) FACTION.models = FACTION.models or CITIZEN_MODELS FACTION.uniqueID = FACTION.uniqueID or niceName for _, v2 in pairs(FACTION.models) do if (isstring(v2)) then util.PrecacheModel(v2) elseif (istable(v2)) then util.PrecacheModel(v2[1]) end end if (!FACTION.GetModels) then function FACTION:GetModels(client) return self.models end end ix.faction.indices[FACTION.index] = FACTION ix.faction.teams[niceName] = FACTION FACTION = nil end end --- Retrieves a faction table. -- @realm shared -- @param identifier Index or name of the faction -- @treturn table Faction table -- @usage print(ix.faction.Get(Entity(1):Team()).name) -- > "Citizen" function ix.faction.Get(identifier) return ix.faction.indices[identifier] or ix.faction.teams[identifier] end --- Retrieves a faction index. -- @realm shared -- @string uniqueID Unique ID of the faction -- @treturn number Faction index function ix.faction.GetIndex(uniqueID) for k, v in ipairs(ix.faction.indices) do if (v.uniqueID == uniqueID) then return k end end end if (CLIENT) then --- Returns true if a faction requires a whitelist. -- @realm client -- @number faction Index of the faction -- @treturn bool Whether or not the faction requires a whitelist function ix.faction.HasWhitelist(faction) local data = ix.faction.indices[faction] if (data) then if (data.isDefault) then return true end local ixData = ix.localData and ix.localData.whitelists or {} return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false end return false end end ================================================ FILE: gamemode/core/libs/sh_flag.lua ================================================ --[[-- Grants abilities to characters. Flags are a simple way of adding/removing certain abilities to players on a per-character basis. Helix comes with a few flags by default, for example to restrict spawning of props, usage of the physgun, etc. All flags will be listed in the `Flags` section of the `Help` menu. Flags are usually used when server validation is required to allow a player to do something on their character. However, it's usually preferable to use in-character methods over flags when possible (i.e restricting the business menu to characters that have a permit item, rather than using flags to determine availability). Flags are a single alphanumeric character that can be checked on the server. Serverside callbacks can be used to provide functionality whenever the flag is added or removed. For example: ix.flag.Add("z", "Access to some cool stuff.", function(client, bGiven) print("z flag given:", bGiven) end) Entity(1):GetCharacter():GiveFlags("z") > z flag given: true Entity(1):GetCharacter():TakeFlags("z") > z flag given: false print(Entity(1):GetCharacter():HasFlags("z")) > false Check out `Character:GiveFlags` and `Character:TakeFlags` for additional info. ]] -- @module ix.flag ix.flag = ix.flag or {} ix.flag.list = ix.flag.list or {} --- Creates a flag. This should be called shared in order for the client to be aware of the flag's existence. -- @realm shared -- @string flag Alphanumeric character to use for the flag -- @string description Description of the flag -- @func callback Function to call when the flag is given or taken from a player function ix.flag.Add(flag, description, callback) ix.flag.list[flag] = { description = description, callback = callback } end if (SERVER) then -- Called to apply flags when a player has spawned. -- @realm server -- @internal -- @player client Player to setup flags for function ix.flag.OnSpawn(client) -- Check if they have a valid character. if (client:GetCharacter()) then -- Get all of the character's flags. local flags = client:GetCharacter():GetFlags() for i = 1, #flags do -- Get each individual flag. local flag = flags[i] local info = ix.flag.list[flag] -- Check if the flag has a callback. if (info and info.callback) then -- Run the callback, passing the player and true so they get whatever benefits. info.callback(client, true) end end end end end do local character = ix.meta.character if (SERVER) then --- Flag util functions for character -- @classmod Character --- Sets this character's accessible flags. Note that this method overwrites **all** flags instead of adding them. -- @realm server -- @string flags Flag(s) this charater is allowed to have -- @see GiveFlags function character:SetFlags(flags) self:SetData("f", flags) end --- Adds a flag to the list of this character's accessible flags. This does not overwrite existing flags. -- @realm server -- @string flags Flag(s) this character should be given -- @usage character:GiveFlags("pet") -- -- gives p, e, and t flags to the character -- @see HasFlags function character:GiveFlags(flags) local addedFlags = "" -- Get the individual flags within the flag string. for i = 1, #flags do local flag = flags[i] local info = ix.flag.list[flag] if (info) then if (!self:HasFlags(flag)) then addedFlags = addedFlags..flag end if (info.callback) then -- Pass the player and true (true for the flag being given.) info.callback(self:GetPlayer(), true) end end end -- Only change the flag string if it is different. if (addedFlags != "") then self:SetFlags(self:GetFlags()..addedFlags) end end --- Removes this character's access to the given flags. -- @realm server -- @string flags Flag(s) to remove from this character -- @usage -- for a character with "pet" flags -- character:TakeFlags("p") -- -- character now has e, and t flags function character:TakeFlags(flags) local oldFlags = self:GetFlags() local newFlags = oldFlags -- Get the individual flags within the flag string. for i = 1, #flags do local flag = flags[i] local info = ix.flag.list[flag] -- Call the callback if the flag has been registered. if (info and info.callback) then -- Pass the player and false (false since the flag is being taken) info.callback(self:GetPlayer(), false) end newFlags = newFlags:gsub(flag, "") end if (newFlags != oldFlags) then self:SetFlags(newFlags) end end end --- Returns all of the flags this character has. -- @realm shared -- @treturn string Flags this character has represented as one string. You can access individual flags by iterating through -- the string letter by letter function character:GetFlags() return self:GetData("f", "") end --- Returns `true` if the character has the given flag(s). -- @realm shared -- @string flags Flag(s) to check access for -- @treturn bool Whether or not this character has access to the given flag(s) function character:HasFlags(flags) local bHasFlag = hook.Run("CharacterHasFlags", self, flags) if (bHasFlag == true) then return true end local flagList = self:GetFlags() for i = 1, #flags do if (flagList:find(flags[i], 1, true)) then return true end end return false end end do ix.flag.Add("p", "Access to the physgun.", function(client, isGiven) if (isGiven) then client:Give("weapon_physgun") client:SelectWeapon("weapon_physgun") else client:StripWeapon("weapon_physgun") end end) ix.flag.Add("t", "Access to the toolgun", function(client, isGiven) if (isGiven) then client:Give("gmod_tool") client:SelectWeapon("gmod_tool") else client:StripWeapon("gmod_tool") end end) ix.flag.Add("c", "Access to spawn chairs.") ix.flag.Add("C", "Access to spawn vehicles.") ix.flag.Add("r", "Access to spawn ragdolls.") ix.flag.Add("e", "Access to spawn props.") ix.flag.Add("n", "Access to spawn NPCs.") end ================================================ FILE: gamemode/core/libs/sh_inventory.lua ================================================ --[[-- Inventory manipulation and helper functions. ]] -- @module ix.inventory ix.inventory = ix.inventory or {} ix.util.Include("helix/gamemode/core/meta/sh_inventory.lua") --- Retrieves an inventory table. -- @realm shared -- @number invID Index of the inventory -- @treturn Inventory Inventory table function ix.inventory.Get(invID) return ix.item.inventories[invID] end function ix.inventory.Create(width, height, id) local inventory = ix.meta.inventory:New(id, width, height) ix.item.inventories[id] = inventory return inventory end --- Loads an inventory and associated items from the database into memory. If you are passing a table into `invID`, it -- requires a table where the key is the inventory ID, and the value is a table of the width and height values. See below -- for an example. -- @realm server -- @param invID Inventory ID or table of inventory IDs -- @number width Width of inventory (this is not used when passing a table to `invID`) -- @number height Height of inventory (this is not used when passing a table to `invID`) -- @func callback Function to call when inventory is restored -- @usage ix.inventory.Restore({ -- [10] = {5, 5}, -- [11] = {7, 4} -- }) -- -- inventories 10 and 11 with sizes (5, 5) and (7, 4) will be loaded function ix.inventory.Restore(invID, width, height, callback) local inventories = {} if (!istable(invID)) then if (!isnumber(invID) or invID < 0) then error("Attempt to restore inventory with an invalid ID!") end inventories[invID] = {width, height} ix.inventory.Create(width, height, invID) else for k, v in pairs(invID) do inventories[k] = {v[1], v[2]} ix.inventory.Create(v[1], v[2], k) end end local query = mysql:Select("ix_items") query:Select("item_id") query:Select("inventory_id") query:Select("unique_id") query:Select("data") query:Select("character_id") query:Select("player_id") query:Select("x") query:Select("y") query:WhereIn("inventory_id", table.GetKeys(inventories)) query:Callback(function(result) if (istable(result) and #result > 0) then local invSlots = {} for _, item in ipairs(result) do local itemInvID = tonumber(item.inventory_id) local invInfo = inventories[itemInvID] if (!itemInvID or !invInfo) then -- don't restore items with an invalid inventory id or type continue end local inventory = ix.item.inventories[itemInvID] local x, y = tonumber(item.x), tonumber(item.y) local itemID = tonumber(item.item_id) local data = util.JSONToTable(item.data or "[]") local characterID, playerID = tonumber(item.character_id), tostring(item.player_id) if (x and y and itemID) then if (x <= inventory.w and x > 0 and y <= inventory.h and y > 0) then local item2 = ix.item.New(item.unique_id, itemID) if (item2) then invSlots[itemInvID] = invSlots[itemInvID] or {} local slots = invSlots[itemInvID] item2.data = {} if (data) then item2.data = data end item2.gridX = x item2.gridY = y item2.invID = itemInvID item2.characterID = characterID item2.playerID = (playerID == "" or playerID == "NULL") and nil or playerID for x2 = 0, item2.width - 1 do for y2 = 0, item2.height - 1 do slots[x + x2] = slots[x + x2] or {} slots[x + x2][y + y2] = item2 end end if (item2.OnRestored) then item2:OnRestored(item2, itemInvID) end end end end end for k, v in pairs(invSlots) do ix.item.inventories[k].slots = v end end if (callback) then for k, _ in pairs(inventories) do callback(ix.item.inventories[k]) end end end) query:Execute() end function ix.inventory.New(owner, invType, callback) local invData = ix.item.inventoryTypes[invType] or {w = 1, h = 1} local query = mysql:Insert("ix_inventories") query:Insert("inventory_type", invType) query:Insert("character_id", owner) query:Callback(function(result, status, lastID) local inventory = ix.inventory.Create(invData.w, invData.h, lastID) if (invType) then inventory.vars.isBag = invType end if (isnumber(owner) and owner > 0) then local character = ix.char.loaded[owner] local client = character:GetPlayer() inventory:SetOwner(owner) if (IsValid(client)) then inventory:Sync(client) end end if (callback) then callback(inventory) end end) query:Execute() end function ix.inventory.Register(invType, w, h, isBag) ix.item.inventoryTypes[invType] = {w = w, h = h} if (isBag) then ix.item.inventoryTypes[invType].isBag = invType end return ix.item.inventoryTypes[invType] end ================================================ FILE: gamemode/core/libs/sh_item.lua ================================================ --[[-- Item manipulation and helper functions. ]] -- @module ix.item ix.item = ix.item or {} ix.item.list = ix.item.list or {} ix.item.base = ix.item.base or {} ix.item.instances = ix.item.instances or {} ix.item.inventories = ix.item.inventories or { [0] = {} } ix.item.inventoryTypes = ix.item.inventoryTypes or {} ix.util.Include("helix/gamemode/core/meta/sh_item.lua") -- Declare some supports for logic inventory local zeroInv = ix.item.inventories[0] function zeroInv:GetID() return 0 end function zeroInv:OnCheckAccess(client) return true end -- WARNING: You have to manually sync the data to client if you're trying to use item in the logical inventory in the vgui. function zeroInv:Add(uniqueID, quantity, data, x, y) quantity = quantity or 1 if (quantity > 0) then if (!isnumber(uniqueID)) then if (quantity > 1) then for _ = 1, quantity do self:Add(uniqueID, 1, data) end return end local itemTable = ix.item.list[uniqueID] if (!itemTable) then return false, "invalidItem" end ix.item.Instance(0, uniqueID, data, x, y, function(item) self[item:GetID()] = item end) return nil, nil, 0 end else return false, "notValid" end end function ix.item.Instance(index, uniqueID, itemData, x, y, callback, characterID, playerID) if (!uniqueID or ix.item.list[uniqueID]) then itemData = istable(itemData) and itemData or {} local query = mysql:Insert("ix_items") query:Insert("inventory_id", index) query:Insert("unique_id", uniqueID) query:Insert("data", util.TableToJSON(itemData)) query:Insert("x", x) query:Insert("y", y) if (characterID) then query:Insert("character_id", characterID) end if (playerID) then query:Insert("player_id", playerID) end query:Callback(function(result, status, lastID) local item = ix.item.New(uniqueID, lastID) if (item) then item.data = table.Copy(itemData) item.invID = index item.characterID = characterID item.playerID = playerID if (callback) then callback(item) end if (item.OnInstanced) then item:OnInstanced(index, x, y, item) end end end) query:Execute() else ErrorNoHalt("[Helix] Attempt to give an invalid item! (" .. (uniqueID or "nil") .. ")\n") end end --- Retrieves an item table. -- @realm shared -- @string identifier Unique ID of the item -- @treturn item Item table -- @usage print(ix.item.Get("example")) -- > "item[example][0]" function ix.item.Get(identifier) return ix.item.base[identifier] or ix.item.list[identifier] end function ix.item.Load(path, baseID, isBaseItem) local uniqueID = path:match("sh_([_%w]+)%.lua") if (uniqueID) then uniqueID = (isBaseItem and "base_" or "")..uniqueID ix.item.Register(uniqueID, baseID, isBaseItem, path) else if (!path:find(".txt")) then ErrorNoHalt("[Helix] Item at '"..path.."' follows invalid naming convention!\n") end end end function ix.item.Register(uniqueID, baseID, isBaseItem, path, luaGenerated) local meta = ix.meta.item if (uniqueID) then ITEM = (isBaseItem and ix.item.base or ix.item.list)[uniqueID] or setmetatable({}, meta) ITEM.uniqueID = uniqueID ITEM.base = baseID or ITEM.base ITEM.isBase = isBaseItem or false ITEM.hooks = ITEM.hooks or {} ITEM.postHooks = ITEM.postHooks or {} ITEM.functions = ITEM.functions or {} ITEM.functions.drop = ITEM.functions.drop or { tip = "dropTip", icon = "icon16/world.png", OnRun = function(item) local bSuccess, error = item:Transfer(nil, nil, nil, item.player) if (!bSuccess and isstring(error)) then item.player:NotifyLocalized(error) else item.player:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1) end return false end, OnCanRun = function(item) return !IsValid(item.entity) and !item.noDrop end } ITEM.functions.take = ITEM.functions.take or { tip = "takeTip", icon = "icon16/box.png", OnRun = function(item) local client = item.player local bSuccess, error = item:Transfer(client:GetCharacter():GetInventory():GetID(), nil, nil, client) if (!bSuccess) then client:NotifyLocalized(error or "unknownError") return false else client:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1) if (item.data) then -- I don't like it but, meh... for k, v in pairs(item.data) do item:SetData(k, v) end end end return true end, OnCanRun = function(item) return IsValid(item.entity) end } local oldBase = ITEM.base if (ITEM.base) then local baseTable = ix.item.base[ITEM.base] if (baseTable) then for k, v in pairs(baseTable) do if (ITEM[k] == nil) then ITEM[k] = v end ITEM.baseTable = baseTable end local mergeTable = table.Copy(baseTable) ITEM = table.Merge(mergeTable, ITEM) else ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n") end end if (PLUGIN) then ITEM.plugin = PLUGIN.uniqueID end if (!luaGenerated and path) then ix.util.Include(path) end if (ITEM.base and oldBase != ITEM.base) then local baseTable = ix.item.base[ITEM.base] if (baseTable) then for k, v in pairs(baseTable) do if (ITEM[k] == nil) then ITEM[k] = v end ITEM.baseTable = baseTable end local mergeTable = table.Copy(baseTable) ITEM = table.Merge(mergeTable, ITEM) else ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n") end end ITEM.description = ITEM.description or "noDesc" ITEM.width = ITEM.width or 1 ITEM.height = ITEM.height or 1 ITEM.category = ITEM.category or "misc" if (ITEM.OnRegistered) then ITEM:OnRegistered() end (isBaseItem and ix.item.base or ix.item.list)[ITEM.uniqueID] = ITEM if (IX_RELOADED) then -- we don't know which item was actually edited, so we'll refresh all of them for _, v in pairs(ix.item.instances) do if (v.uniqueID == uniqueID) then ix.util.MetatableSafeTableMerge(v, ITEM) end end end if (luaGenerated) then return ITEM else ITEM = nil end else ErrorNoHalt("[Helix] You tried to register an item without uniqueID!\n") end end function ix.item.LoadFromDir(directory) local files, folders files = file.Find(directory.."/base/*.lua", "LUA") for _, v in ipairs(files) do ix.item.Load(directory.."/base/"..v, nil, true) end files, folders = file.Find(directory.."/*", "LUA") for _, v in ipairs(folders) do if (v == "base") then continue end for _, v2 in ipairs(file.Find(directory.."/"..v.."/*.lua", "LUA")) do ix.item.Load(directory.."/"..v .. "/".. v2, "base_"..v) end end for _, v in ipairs(files) do ix.item.Load(directory.."/"..v) end end function ix.item.New(uniqueID, id) if (ix.item.instances[id] and ix.item.instances[id].uniqueID == uniqueID) then return ix.item.instances[id] end local stockItem = ix.item.list[uniqueID] if (stockItem) then local item = setmetatable({id = id, data = {}}, { __index = stockItem, __eq = stockItem.__eq, __tostring = stockItem.__tostring }) ix.item.instances[id] = item return item else ErrorNoHalt("[Helix] Attempt to index unknown item '"..uniqueID.."'\n") end end do function ix.item.GetInv(invID) ErrorNoHalt("ix.item.GetInv is deprecated. Use ix.inventory.Get instead!\n") return ix.inventory.Get(invID) end function ix.item.RegisterInv(invType, w, h, isBag) ErrorNoHalt("ix.item.RegisterInv is deprecated. Use ix.inventory.Register instead!\n") return ix.inventory.Register(invType, w, h, isBag) end function ix.item.NewInv(owner, invType, callback) ErrorNoHalt("ix.item.NewInv is deprecated. Use ix.inventory.New instead!\n") return ix.inventory.New(owner, invType, callback) end function ix.item.CreateInv(width, height, id) ErrorNoHalt("ix.item.CreateInv is deprecated. Use ix.inventory.Create instead!\n") return ix.inventory.Create(width, height, id) end function ix.item.RestoreInv(invID, width, height, callback) ErrorNoHalt("ix.item.RestoreInv is deprecated. Use ix.inventory.Restore instead!\n") return ix.inventory.Restore(invID, width, height, callback) end if (CLIENT) then net.Receive("ixInventorySync", function() local slots = net.ReadTable() local id = net.ReadUInt(32) local w, h = net.ReadUInt(6), net.ReadUInt(6) local owner = net.ReadType() local vars = net.ReadTable() if (!LocalPlayer():GetCharacter()) then return end local character = owner and ix.char.loaded[owner] local inventory = ix.inventory.Create(w, h, id) inventory.slots = {} inventory.vars = vars local x, y for _, v in ipairs(slots) do x, y = v[1], v[2] inventory.slots[x] = inventory.slots[x] or {} local item = ix.item.New(v[3], v[4]) item.data = {} if (v[5]) then item.data = v[5] end item.invID = item.invID or id inventory.slots[x][y] = item end if (character) then inventory:SetOwner(character:GetID()) character.vars.inv = character.vars.inv or {} for k, v in ipairs(character:GetInventory(true)) do if (v:GetID() == id) then character:GetInventory(true)[k] = inventory return end end table.insert(character.vars.inv, inventory) end end) net.Receive("ixInventoryData", function() local id = net.ReadUInt(32) local item = ix.item.instances[id] if (item) then local key = net.ReadString() local value = net.ReadType() item.data = item.data or {} item.data[key] = value local invID = item.invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or item.invID local panel = ix.gui["inv" .. invID] if (panel and panel.panels) then local icon = panel.panels[id] if (icon) then icon:SetHelixTooltip(function(tooltip) ix.hud.PopulateItemTooltip(tooltip, item) end) end end end end) net.Receive("ixInventorySet", function() local invID = net.ReadUInt(32) local x, y = net.ReadUInt(6), net.ReadUInt(6) local uniqueID = net.ReadString() local id = net.ReadUInt(32) local owner = net.ReadUInt(32) local data = net.ReadTable() local character = owner != 0 and ix.char.loaded[owner] or LocalPlayer():GetCharacter() if (character) then local inventory = ix.item.inventories[invID] if (inventory) then local item = (uniqueID != "" and id != 0) and ix.item.New(uniqueID, id) or nil item.invID = invID item.data = {} if (data) then item.data = data end inventory.slots[x] = inventory.slots[x] or {} inventory.slots[x][y] = item invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID local panel = ix.gui["inv" .. invID] if (IsValid(panel)) then local icon = panel:AddIcon( item:GetModel() or "models/props_junk/popcan01a.mdl", x, y, item.width, item.height, item:GetSkin() ) if (IsValid(icon)) then icon:SetHelixTooltip(function(tooltip) ix.hud.PopulateItemTooltip(tooltip, item) end) icon.itemID = item.id panel.panels[item.id] = icon end end end end end) net.Receive("ixInventoryMove", function() local invID = net.ReadUInt(32) local inventory = ix.item.inventories[invID] if (!inventory) then return end local itemID = net.ReadUInt(32) local oldX = net.ReadUInt(6) local oldY = net.ReadUInt(6) local x = net.ReadUInt(6) local y = net.ReadUInt(6) invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID local item = ix.item.instances[itemID] local panel = ix.gui["inv" .. invID] -- update inventory UI if it's open if (IsValid(panel)) then local icon = panel.panels[itemID] if (IsValid(icon)) then icon:Move(x, y, panel, true) end end -- update inventory slots if (item) then inventory.slots[oldX][oldY] = nil inventory.slots[x] = inventory.slots[x] or {} inventory.slots[x][y] = item end end) net.Receive("ixInventoryRemove", function() local id = net.ReadUInt(32) local invID = net.ReadUInt(32) local inventory = ix.item.inventories[invID] if (!inventory) then return end inventory:Remove(id) invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID local panel = ix.gui["inv" .. invID] if (IsValid(panel)) then local icon = panel.panels[id] if (IsValid(icon)) then for _, v in ipairs(icon.slots or {}) do if (v.item == icon) then v.item = nil end end icon:Remove() end end local item = ix.item.instances[id] if (!item) then return end -- we need to close any bag windows that are open because of this item if (item.isBag) then local itemInv = item:GetInventory() if (itemInv) then local frame = ix.gui["inv" .. itemInv:GetID()] if (IsValid(frame)) then frame:Remove() end end end end) else util.AddNetworkString("ixInventorySync") util.AddNetworkString("ixInventorySet") util.AddNetworkString("ixInventoryMove") util.AddNetworkString("ixInventoryRemove") util.AddNetworkString("ixInventoryData") util.AddNetworkString("ixInventoryAction") function ix.item.LoadItemByID(itemIndex, recipientFilter) local query = mysql:Select("ix_items") query:Select("item_id") query:Select("unique_id") query:Select("data") query:Select("character_id") query:Select("player_id") query:WhereIn("item_id", itemIndex) query:Callback(function(result) if (istable(result)) then for _, v in ipairs(result) do local itemID = tonumber(v.item_id) local data = util.JSONToTable(v.data or "[]") local uniqueID = v.unique_id local itemTable = ix.item.list[uniqueID] local characterID = tonumber(v.character_id) local playerID = tostring(v.player_id) if (itemTable and itemID) then local item = ix.item.New(uniqueID, itemID) item.data = data or {} item.invID = 0 item.characterID = characterID item.playerID = (playerID == "" or playerID == "NULL") and nil or playerID end end end end) query:Execute() end function ix.item.PerformInventoryAction(client, action, item, invID, data) local character = client:GetCharacter() if (!character) then return end local inventory = ix.item.inventories[invID or 0] if (hook.Run("CanPlayerInteractItem", client, action, item, data) == false) then return end if (!inventory:OnCheckAccess(client)) then return end if (isentity(item)) then if (IsValid(item)) then local entity = item local itemID = item.ixItemID item = ix.item.instances[itemID] if (!item) then return end item.entity = entity item.player = client else return end elseif (isnumber(item)) then item = ix.item.instances[item] if (!item) then return end item.player = client end if (item.entity) then if (client:GetShootPos():DistToSqr(item.entity:GetPos()) > 96 * 96) then return end local useEntity = client:GetUseEntity() if (IsValid(useEntity) and item.entity != useEntity) then return end elseif (!inventory:GetItemByID(item.id)) then return end if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then local itemPlayerID = item:GetPlayerID() local itemCharacterID = item:GetCharacterID() local playerID = client:SteamID64() local characterID = client:GetCharacter():GetID() if (itemPlayerID and itemCharacterID and itemPlayerID == playerID and itemCharacterID != characterID) then client:NotifyLocalized("itemOwned") item.player = nil item.entity = nil return end end local callback = item.functions[action] if (callback) then if (callback.OnCanRun and callback.OnCanRun(item, data) == false) then item.entity = nil item.player = nil return end hook.Run("PlayerInteractItem", client, action, item) local entity = item.entity local result if (item.hooks[action]) then result = item.hooks[action](item, data) end if (result == nil) then result = callback.OnRun(item, data) end if (item.postHooks[action]) then -- Posthooks shouldn't override the result from OnRun item.postHooks[action](item, result, data) end if (result != false) then if (IsValid(entity)) then entity.ixIsSafe = true entity:Remove() else item:Remove() end end item.entity = nil item.player = nil return result != false end end local function NetworkInventoryMove(receiver, invID, itemID, oldX, oldY, x, y) net.Start("ixInventoryMove") net.WriteUInt(invID, 32) net.WriteUInt(itemID, 32) net.WriteUInt(oldX, 6) net.WriteUInt(oldY, 6) net.WriteUInt(x, 6) net.WriteUInt(y, 6) net.Send(receiver) end net.Receive("ixInventoryMove", function(length, client) local oldX, oldY, x, y = net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6) local invID, newInvID = net.ReadUInt(32), net.ReadUInt(32) local character = client:GetCharacter() if (character) then local inventory = ix.item.inventories[invID] if (!inventory) then return end if ((inventory.owner and inventory.owner == character:GetID()) or inventory:OnCheckAccess(client)) then local item = inventory:GetItemAt(oldX, oldY) if (item) then if (newInvID and invID != newInvID) then local inventory2 = ix.item.inventories[newInvID] if (inventory2) then local bStatus, error = item:Transfer(newInvID, x, y, client) if (!bStatus) then NetworkInventoryMove( client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY ) client:NotifyLocalized(error or "unknownError") end end return end if (inventory:CanItemFit(x, y, item.width, item.height, item)) then item.gridX = x item.gridY = y for x2 = 0, item.width - 1 do for y2 = 0, item.height - 1 do local previousX = inventory.slots[oldX + x2] if (previousX) then previousX[oldY + y2] = nil end end end for x2 = 0, item.width - 1 do for y2 = 0, item.height - 1 do inventory.slots[x + x2] = inventory.slots[x + x2] or {} inventory.slots[x + x2][y + y2] = item end end local receivers = inventory:GetReceivers() if (istable(receivers)) then local filtered = {} for _, v in ipairs(receivers) do if (v != client) then filtered[#filtered + 1] = v end end if (#filtered > 0) then NetworkInventoryMove( filtered, invID, item:GetID(), oldX, oldY, x, y ) end end if (!inventory.noSave) then local query = mysql:Update("ix_items") query:Update("x", x) query:Update("y", y) query:Where("item_id", item.id) query:Execute() end else NetworkInventoryMove( client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY ) end end else local item = inventory:GetItemAt(oldX, oldY) if (item) then NetworkInventoryMove( client, item.invID, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY ) end end end end) net.Receive("ixInventoryAction", function(length, client) ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadUInt(32), net.ReadUInt(32), net.ReadTable()) end) end --- Instances and spawns a given item type. -- @realm server -- @string uniqueID Unique ID of the item -- @vector position The position in which the item's entity will be spawned -- @func[opt=nil] callback Function to call when the item entity is created -- @angle[opt=angle_zero] angles The angles at which the item's entity will spawn -- @tab[opt=nil] data Additional data for this item instance function ix.item.Spawn(uniqueID, position, callback, angles, data) ix.item.Instance(0, uniqueID, data or {}, 1, 1, function(item) local entity = item:Spawn(position, angles) if (callback) then callback(item, entity) end end) end end --- Inventory util functions for character -- @classmod Character --- Returns this character's associated `Inventory` object. -- @function GetInventory -- @realm shared -- @treturn Inventory This character's inventory ix.char.RegisterVar("Inventory", { bNoNetworking = true, bNoDisplay = true, OnGet = function(character, index) if (index and !isnumber(index)) then return character.vars.inv or {} end return character.vars.inv and character.vars.inv[index or 1] end, alias = "Inv" }) ================================================ FILE: gamemode/core/libs/sh_language.lua ================================================ --[[-- Multi-language phrase support. Helix has support for multiple languages, and you can easily leverage this system for use in your own schema, plugins, etc. Languages will be loaded from the schema and any plugins in `languages/sh_languagename.lua`, where `languagename` is the id of a language (`english` for English, `french` for French, etc). The structure of a language file is a table of phrases with the key as its phrase ID and the value as its translation for that language. For example, in `plugins/area/languages/sh_english.lua`: LANGUAGE = { area = "Area", areas = "Areas", areaEditMode = "Area Edit Mode", -- etc. } The phrases defined in these language files can be used with the `L` global function: print(L("areaEditMode")) > Area Edit Mode All phrases are formatted with `string.format`, so if you wish to add some info in a phrase you can use standard Lua string formatting arguments: print(L("areaDeleteConfirm", "Test")) > Are you sure you want to delete the area "Test"? Phrases are also usable on the server, but only when trying to localize a phrase based on a client's preferences. The server does not have a set language. An example: Entity(1):ChatPrint(L("areaEditMode")) > -- "Area Edit Mode" will print in the player's chatbox ]] -- @module ix.lang ix.lang = ix.lang or {} ix.lang.stored = ix.lang.stored or {} ix.lang.names = ix.lang.names or {} --- Loads language files from a directory. -- @realm shared -- @internal -- @string directory Directory to load language files from function ix.lang.LoadFromDir(directory) for _, v in ipairs(file.Find(directory.."/sh_*.lua", "LUA")) do local niceName = v:sub(4, -5):lower() ix.util.Include(directory.."/"..v, "shared") if (LANGUAGE) then if (NAME) then ix.lang.names[niceName] = NAME NAME = nil end ix.lang.AddTable(niceName, LANGUAGE) LANGUAGE = nil end end end --- Adds phrases to a language. This is used when you aren't adding entries through the files in the `languages/` folder. A -- common use case is adding language phrases in a single-file plugin. -- @realm shared -- @string language The ID of the language -- @tab data Language data to add to the given language -- @usage ix.lang.AddTable("english", { -- myPhrase = "My Phrase" -- }) function ix.lang.AddTable(language, data) language = tostring(language):lower() ix.lang.stored[language] = table.Merge(ix.lang.stored[language] or {}, data) end if (SERVER) then -- luacheck: globals L function L(key, client, ...) local languages = ix.lang.stored local langKey = ix.option.Get(client, "language", "english") local info = languages[langKey] or languages.english return string.format(info and info[key] or languages.english[key] or key, ...) end -- luacheck: globals L2 function L2(key, client, ...) local languages = ix.lang.stored local langKey = ix.option.Get(client, "language", "english") local info = languages[langKey] or languages.english if (info and info[key]) then return string.format(info[key], ...) end end else function L(key, ...) local languages = ix.lang.stored local langKey = ix.option.Get("language", "english") local info = languages[langKey] or languages.english return string.format(info and info[key] or languages.english[key] or key, ...) end function L2(key, ...) local langKey = ix.option.Get("language", "english") local info = ix.lang.stored[langKey] if (info and info[key]) then return string.format(info[key], ...) end end end ================================================ FILE: gamemode/core/libs/sh_log.lua ================================================ --[[-- Logging helper functions. Predefined flags: FLAG_NORMAL FLAG_SUCCESS FLAG_WARNING FLAG_DANGER FLAG_SERVER FLAG_DEV ]] -- @module ix.log -- luacheck: globals FLAG_NORMAL FLAG_SUCCESS FLAG_WARNING FLAG_DANGER FLAG_SERVER FLAG_DEV FLAG_NORMAL = 0 FLAG_SUCCESS = 1 FLAG_WARNING = 2 FLAG_DANGER = 3 FLAG_SERVER = 4 FLAG_DEV = 5 ix.log = ix.log or {} ix.log.color = { [FLAG_NORMAL] = Color(200, 200, 200), [FLAG_SUCCESS] = Color(50, 200, 50), [FLAG_WARNING] = Color(255, 255, 0), [FLAG_DANGER] = Color(255, 50, 50), [FLAG_SERVER] = Color(200, 200, 220), [FLAG_DEV] = Color(200, 200, 220), } CAMI.RegisterPrivilege({ Name = "Helix - Logs", MinAccess = "admin" }) local consoleColor = Color(50, 200, 50) if (SERVER) then if (!ix.db) then include("sv_database.lua") end util.AddNetworkString("ixLogStream") function ix.log.LoadTables() ix.log.CallHandler("Load") end ix.log.types = ix.log.types or {} --- Registers a log type for `ix.log.Add`. -- `format` is either a format string or a function `(client, ...) -> string`. -- -- `flag` is a display/severity tag (see `FLAG_*`). If omitted, it behaves like a normal log. -- @realm server -- @tparam string logType Type name used when calling `ix.log.Add`. -- @tparam string|function format Format string or formatter function. -- @tparam[opt] number flag One of the `FLAG_*` constants. -- @usage -- ix.log.AddType("charMoneyGive", "%s gave %s %d tokens.", FLAG_SUCCESS) function ix.log.AddType(logType, format, flag) ix.log.types[logType] = {format = format, flag = flag} end --- Turns a log type + args into `(message, flag)`. -- Missing type falls back to a warning string. No formatter returns `-1` (caller bails). -- @internal -- @realm server -- @tparam Player|nil client Player tied to the log. -- @tparam string logType Log type name. -- @param ... Arguments for the formatter. -- @treturn string|number Message string, or -1 to skip logging. -- @treturn number Flag from the registered type. function ix.log.Parse(client, logType, ...) local info = ix.log.types[logType] if (!info) then ErrorNoHalt("attempted to add entry to non-existent log type \"" .. tostring(logType) .. "\"\n") local fallback = logType.." : " if (client) then fallback = fallback..client:Name().." - " end for _, v in ipairs({...}) do fallback = fallback..tostring(v).." " end return fallback, FLAG_WARNING end local text = info and info.format if (text) then if (isfunction(text)) then text = text(client, ...) end else text = -1 end return text, info.flag end --- Adds a log entry without using a log type. -- Sends the message to admins with log access, prints it to the server console, -- and writes it through log handlers unless `bNoSave` is true. -- @realm server -- @tparam string logString Pre-formatted log message. -- @tparam[opt] boolean bNoSave If true, skips handler output (e.g. file logging). -- @usage ix.log.AddRaw("Server is restarting in 5 minutes.") -- @see ix.log.Add function ix.log.AddRaw(logString, bNoSave) CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers) ix.log.Send(receivers, logString) end) Msg("[LOG] ", logString .. "\n") if (!bNoSave) then ix.log.CallHandler("Write", nil, logString) end end --- Adds a typed log entry. -- The entry is visible to admins with log access, printed to the server console, -- and written through the logging system. -- @realm server -- @tparam Player|nil client Player tied to the log (can be nil). -- @tparam string logType Type registered with `ix.log.AddType`. -- @param ... Arguments for the formatter. -- @usage ix.log.Add(client, "charMoneyGive", giverName, receiverName, amount) -- @see ix.log.AddType -- @see ix.log.AddRaw function ix.log.Add(client, logType, ...) local logString, logFlag = ix.log.Parse(client, logType, ...) if (logString == -1) then return end CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers) ix.log.Send(receivers, logString, logFlag) end) Msg("[LOG] ", logString .. "\n") ix.log.CallHandler("Write", client, logString, logFlag, logType, {...}) end --- Sends a log entry to clients. -- @internal -- @realm server -- @tparam Player|table receivers Target player(s). -- @tparam string logString Log message. -- @tparam[opt] number flag `FLAG_*` value (defaults to normal). function ix.log.Send(receivers, logString, flag) net.Start("ixLogStream") net.WriteString(logString) net.WriteUInt(flag or 0, 4) net.Send(receivers) end ix.log.handlers = ix.log.handlers or {} --- Calls a specific event on all registered log handlers. -- @realm server -- @internal function ix.log.CallHandler(event, ...) for _, v in pairs(ix.log.handlers) do if (isfunction(v[event])) then v[event](...) end end end -- Register a log handler -- @realm server -- @internal function ix.log.RegisterHandler(name, data) data.name = string.gsub(name, "%s", "") name = name:lower() data.uniqueID = name ix.log.handlers[name] = data end do local HANDLER = {} function HANDLER.Load() file.CreateDir("helix/logs") end function HANDLER.Write(client, message) file.Append("helix/logs/" .. os.date("%x"):gsub("/", "-") .. ".txt", "[" .. os.date("%X") .. "]\t" .. message .. "\r\n") end ix.log.RegisterHandler("File", HANDLER) end else net.Receive("ixLogStream", function(length) local logString = net.ReadString() local flag = net.ReadUInt(4) if (isstring(logString) and isnumber(flag)) then MsgC(consoleColor, "[SERVER] ", ix.log.color[flag], logString .. "\n") end end) end ================================================ FILE: gamemode/core/libs/sh_menu.lua ================================================ --[[-- Entity menu manipulation. The `menu` library allows you to open up a context menu of arbitrary options whose callbacks will be ran when they are selected from the panel that shows up for the player. ]] -- @module ix.menu --- You'll need to pass a table of options to `ix.menu.Open` to populate the menu. This table consists of strings as its keys -- and functions as its values. These correspond to the text displayed in the menu and the callback to run, respectively. -- -- Example usage: -- ix.menu.Open({ -- Drink = function() -- print("Drink option selected!") -- end, -- Take = function() -- print("Take option selected!") -- end -- }, ents.GetByIndex(1)) -- This opens a menu with the options `"Drink"` and `"Take"` which will print a message when you click on either of the options. -- @realm client -- @table MenuOptionsStructure ix.menu = ix.menu or {} if (CLIENT) then --- Opens up a context menu for the given entity. -- @realm client -- @tparam MenuOptionsStructure options Data describing what options to display -- @entity[opt] entity Entity to send commands to -- @treturn boolean Whether or not the menu opened successfully. It will fail when there is already a menu open. function ix.menu.Open(options, entity) if (IsValid(ix.menu.panel)) then return false end local panel = vgui.Create("ixEntityMenu") panel:SetEntity(entity) panel:SetOptions(options) return true end --- Checks whether or not an entity menu is currently open. -- @realm client -- @treturn boolean Whether or not an entity menu is open function ix.menu.IsOpen() return IsValid(ix.menu.panel) end --- Notifies the server of an option that was chosen for the given entity. -- @realm client -- @entity entity Entity to call option on -- @string choice Option that was chosen -- @param data Extra data to send to the entity function ix.menu.NetworkChoice(entity, choice, data) if (IsValid(entity)) then net.Start("ixEntityMenuSelect") net.WriteEntity(entity) net.WriteString(choice) net.WriteType(data) net.SendToServer() end end else util.AddNetworkString("ixEntityMenuSelect") net.Receive("ixEntityMenuSelect", function(length, client) local entity = net.ReadEntity() local option = net.ReadString() local data = net.ReadType() if (!IsValid(entity) or !isstring(option) or hook.Run("CanPlayerInteractEntity", client, entity, option, data) == false ) then return end hook.Run("PlayerInteractEntity", client, entity, option, data) local callbackName = "OnSelect" .. option:gsub("%s", "") if (entity[callbackName]) then entity[callbackName](entity, client, data) else entity:OnOptionSelected(client, option, data) end end) end do local PLAYER = FindMetaTable("Player") if (CLIENT) then function PLAYER:GetEntityMenu() local options = {} hook.Run("GetPlayerEntityMenu", self, options) return options end else function PLAYER:OnOptionSelected(client, option) hook.Run("OnPlayerOptionSelected", self, client, option) end end end ================================================ FILE: gamemode/core/libs/sh_notice.lua ================================================ --- Notification helper functions -- @module ix.notice if (SERVER) then util.AddNetworkString("ixNotify") util.AddNetworkString("ixNotifyLocalized") --- Sends a notification to a specified recipient. -- @realm server -- @string message Message to notify -- @player[opt=nil] recipient Player to be notified function ix.util.Notify(message, recipient) net.Start("ixNotify") net.WriteString(message) if (recipient == nil) then net.Broadcast() else net.Send(recipient) end end --- Sends a translated notification to a specified recipient. -- @realm server -- @string message Message to notify -- @player[opt=nil] recipient Player to be notified -- @param ... Arguments to pass to the translated message function ix.util.NotifyLocalized(message, recipient, ...) net.Start("ixNotifyLocalized") net.WriteString(message) net.WriteTable({...}) if (recipient == nil) then net.Broadcast() else net.Send(recipient) end end do --- Notification util functions for players -- @classmod Player local playerMeta = FindMetaTable("Player") --- Displays a prominent notification in the top-right of this player's screen. -- @realm shared -- @string message Text to display in the notification function playerMeta:Notify(message) ix.util.Notify(message, self) end --- Displays a notification for this player with the given language phrase. -- @realm shared -- @string message ID of the phrase to display to the player -- @param ... Arguments to pass to the phrase -- @usage client:NotifyLocalized("mapRestarting", 10) -- -- displays "The map will restart in 10 seconds!" if the player's language is set to English -- @see ix.lang function playerMeta:NotifyLocalized(message, ...) ix.util.NotifyLocalized(message, self, ...) end --- Displays a notification for this player in the chatbox. -- @realm shared -- @string message Text to display in the notification function playerMeta:ChatNotify(message) local messageLength = message:utf8len() ix.chat.Send(nil, "notice", message, false, {self}, { bError = message:utf8sub(messageLength, messageLength) == "!" }) end --- Displays a notification for this player in the chatbox with the given language phrase. -- @realm shared -- @string message ID of the phrase to display to the player -- @param ... Arguments to pass to the phrase -- @see NotifyLocalized function playerMeta:ChatNotifyLocalized(message, ...) message = L(message, self, ...) local messageLength = message:utf8len() ix.chat.Send(nil, "notice", message, false, {self}, { bError = message:utf8sub(messageLength, messageLength) == "!" }) end end else -- Create a notification panel. function ix.util.Notify(message) if (ix.option.Get("chatNotices", false)) then local messageLength = message:utf8len() ix.chat.Send(LocalPlayer(), "notice", message, false, { bError = message:utf8sub(messageLength, messageLength) == "!" }) return end if (IsValid(ix.gui.notices)) then ix.gui.notices:AddNotice(message) end MsgC(Color(0, 255, 255), message .. "\n") end -- Creates a translated notification. function ix.util.NotifyLocalized(message, ...) ix.util.Notify(L(message, ...)) end -- shortcut notify functions do local playerMeta = FindMetaTable("Player") function playerMeta:Notify(message) if (self == LocalPlayer()) then ix.util.Notify(message) end end function playerMeta:NotifyLocalized(message, ...) if (self == LocalPlayer()) then ix.util.NotifyLocalized(message, ...) end end function playerMeta:ChatNotify(message) if (self == LocalPlayer()) then local messageLength = message:utf8len() ix.chat.Send(LocalPlayer(), "notice", message, false, { bError = message:utf8sub(messageLength, messageLength) == "!" }) end end function playerMeta:ChatNotifyLocalized(message, ...) if (self == LocalPlayer()) then message = L(message, ...) local messageLength = message:utf8len() ix.chat.Send(LocalPlayer(), "notice", message, false, { bError = message:utf8sub(messageLength, messageLength) == "!" }) end end end -- Receives a notification from the server. net.Receive("ixNotify", function() ix.util.Notify(net.ReadString()) end) -- Receives a notification from the server. net.Receive("ixNotifyLocalized", function() ix.util.NotifyLocalized(net.ReadString(), unpack(net.ReadTable())) end) end ================================================ FILE: gamemode/core/libs/sh_option.lua ================================================ --[[-- Client-side configuration management. The `option` library provides a cleaner way to manage any arbitrary data on the client without the hassle of managing CVars. It is analagous to the `ix.config` library, but it only deals with data that needs to be stored on the client. To get started, you'll need to define an option in a client realm so the framework can be aware of its existence. This can be done in the `cl_init.lua` file of your schema, or in an `if (CLIENT) then` statement in the `sh_plugin.lua` file of your plugin: ix.option.Add("headbob", ix.type.bool, true) If you need to get the value of an option on the server, you'll need to specify `true` for the `bNetworked` argument in `ix.option.Add`. *NOTE:* You also need to define your option in a *shared* realm, since the server now also needs to be aware of its existence. This makes it so that the client will send that option's value to the server whenever it changes, which then means that the server can now retrieve the value that the client has the option set to. For example, if you need to get what language a client is using, you can simply do the following: ix.option.Get(player.GetByID(1), "language", "english") This will return the language of the player, or `"english"` if one isn't found. Note that `"language"` is a networked option that is already defined in the framework, so it will always be available. All options will show up in the options menu on the client, unless `hidden` returns `true` when using `ix.option.Add`. Note that the labels for each option in the menu will use a language phrase to show the name. For example, if your option is named `headbob`, then you'll need to define a language phrase called `optHeadbob` that will be used as the option title. ]] -- @module ix.option ix.option = ix.option or {} ix.option.stored = ix.option.stored or {} ix.option.categories = ix.option.categories or {} --- Creates a client-side configuration option with the given information. -- @realm shared -- @string key Unique ID for this option -- @ixtype optionType Type of this option -- @param default Default value that this option will have - this can be nil if needed -- @tparam OptionStructure data Additional settings for this option -- @usage ix.option.Add("animationScale", ix.type.number, 1, { -- category = "appearance", -- min = 0.3, -- max = 2, -- decimals = 1 -- }) function ix.option.Add(key, optionType, default, data) assert(isstring(key) and key:find("%S"), "expected a non-empty string for the key") data = data or {} local oldOption = ix.option.stored[key] local categories = ix.option.categories local category = data.category or "misc" local upperName = key:sub(1, 1):upper() .. key:sub(2) categories[category] = categories[category] or {} categories[category][key] = true -- using explicit nil comparisons so we don't get caught by a config's value being `false` if (oldOption != nil and oldOption.default != nil) then default = oldOption.default end --- You can specify additional optional arguments for `ix.option.Add` by passing in a table of specific fields as the fourth -- argument. -- @table OptionStructure -- @realm shared -- @field[type=string,opt="opt" .. key] phrase The phrase to use when displaying in the UI. The default value is your option -- key in UpperCamelCase, prefixed with `"opt"`. For example, if your key is `"exampleOption"`, the default phrase will be -- `"optExampleOption"`. -- @field[type=string,opt="optd" .. key] description The phrase to use in the tooltip when hovered in the UI. The default -- value is your option key in UpperCamelCase, prefixed with `"optd"`. For example, if your key is `"exampleOption"`, the -- default phrase will be `"optdExampleOption"`. -- @field[type=string,opt="misc"] category The category that this option should reside in. This is purely for -- aesthetic reasons when displaying the options in the options menu. When displayed in the UI, it will take the form of -- `L("category name")`. This means that you must create a language phrase for the category name - otherwise it will only -- show as the exact string you've specified. If no category is set, it will default to `"misc"`. -- @field[type=number,opt=0] min The minimum allowed amount when setting this option. This field is not -- applicable to any type other than `ix.type.number`. -- @field[type=number,opt=10] max The maximum allowed amount when setting this option. This field is not -- applicable to any type other than `ix.type.number`. -- @field[type=number,opt=0] decimals How many decimals to constrain to when using a number type. This field is not -- applicable to any type other than `ix.type.number`. -- @field[type=boolean,opt=false] bNetworked Whether or not the server should be aware of this option for each client. -- @field[type=function,opt] OnChanged The function to run when this option is changed - this includes whether it was set -- by the player, or through code using `ix.option.Set`. -- OnChanged = function(oldValue, value) -- print("new value is", value) -- end -- @field[type=function,opt] hidden The function to check whether the option should be hidden from the options menu. -- @field[type=function,opt] populate The function to run when the option needs to be added to the menu. This is a required -- field for any array options. It should return a table of entries where the key is the value to set in `ix.option.Set`, -- and the value is the display name for the entry. An example: -- -- populate = function() -- return { -- ["english"] = "English", -- ["french"] = "French", -- ["spanish"] = "Spanish" -- } -- end ix.option.stored[key] = { key = key, phrase = data.phrase or "opt" .. upperName, description = data.description or "optd" .. upperName, type = optionType, default = default, min = data.min or 0, max = data.max or 10, decimals = data.decimals or 0, category = data.category or "misc", bNetworked = data.bNetworked and true or false, hidden = data.hidden or nil, populate = data.populate or nil, OnChanged = data.OnChanged or nil } end --- Sets the default value for a user option. -- @realm shared -- @string key Unique ID of the option -- @param value Default value for the user option function ix.option.SetDefault(key, value) local option = ix.option.stored[key] if (option) then option.default = value else -- set up dummy option if we're setting default of option that doesn't exist yet (i.e schema setting framework default) ix.option.stored[key] = { default = value } end end --- Loads all saved options from disk. -- @realm shared -- @internal function ix.option.Load() ix.util.Include("helix/gamemode/config/sh_options.lua") if (CLIENT) then local options = ix.data.Get("options", nil, true, true) if (options) then for k, v in pairs(options) do ix.option.client[k] = v end end ix.option.Sync() end end --- Returns all of the available options. Note that this does contain the actual values of the options, just their properties. -- @realm shared -- @treturn table Table of all options -- @usage PrintTable(ix.option.GetAll()) -- > language: -- > bNetworked = true -- > default = english -- > type = 512 -- -- etc. function ix.option.GetAll() return ix.option.stored end --- Returns all of the available options grouped by their categories. The returned table contains category tables, that contain -- all the options in that category as an array (this is so you can sort them if you'd like). -- @realm shared -- @bool[opt=false] bRemoveHidden Remove entries that are marked as hidden -- @treturn table Table of all options -- @usage PrintTable(ix.option.GetAllByCategories()) -- > general: -- > 1: -- > key = language -- > bNetworked = true -- > default = english -- > type = 512 -- -- etc. function ix.option.GetAllByCategories(bRemoveHidden) local result = {} for k, v in pairs(ix.option.categories) do for k2, _ in pairs(v) do local option = ix.option.stored[k2] if (bRemoveHidden and isfunction(option.hidden) and option.hidden()) then continue end -- we create the category table here because it could contain all hidden options which makes the table empty result[k] = result[k] or {} result[k][#result[k] + 1] = option end end return result end if (CLIENT) then ix.option.client = ix.option.client or {} --- Sets an option value for the local player. -- This function will error when an invalid key is passed. -- @realm client -- @string key Unique ID of the option -- @param value New value to assign to the option -- @bool[opt=false] bNoSave Whether or not to avoid saving function ix.option.Set(key, value, bNoSave) local option = assert(ix.option.stored[key], "invalid option key \"" .. tostring(key) .. "\"") if (option.type == ix.type.number) then value = math.Clamp(math.Round(value, option.decimals), option.min, option.max) end local oldValue = ix.option.client[key] ix.option.client[key] = value if (option.bNetworked) then net.Start("ixOptionSet") net.WriteString(key) net.WriteType(value) net.SendToServer() end if (!bNoSave) then ix.option.Save() end if (isfunction(option.OnChanged)) then option.OnChanged(oldValue, value) end end --- Retrieves an option value for the local player. If it is not set, it'll return the default that you've specified. -- @realm client -- @string key Unique ID of the option -- @param default Default value to return if the option is not set -- @return[1] Value associated with the key -- @return[2] The given default if the option is not set function ix.option.Get(key, default) local option = ix.option.stored[key] if (option) then local localValue = ix.option.client[key] if (localValue != nil) then return localValue end return option.default end return default end --- Saves all options to disk. -- @realm client -- @internal function ix.option.Save() ix.data.Set("options", ix.option.client, true, true) end --- Syncs all networked options to the server. -- @realm client function ix.option.Sync() local options = {} for k, v in pairs(ix.option.stored) do if (v.bNetworked) then options[#options + 1] = {k, ix.option.client[k]} end end if (#options > 0) then net.Start("ixOptionSync") net.WriteUInt(#options, 8) for _, v in ipairs(options) do net.WriteString(v[1]) net.WriteType(v[2]) end net.SendToServer() end end else util.AddNetworkString("ixOptionSet") util.AddNetworkString("ixOptionSync") ix.option.clients = ix.option.clients or {} --- Retrieves an option value from the specified player. If it is not set, it'll return the default that you've specified. -- This function will error when an invalid player is passed. -- @realm server -- @player client Player to retrieve option value from -- @string key Unique ID of the option -- @param default Default value to return if the option is not set -- @return[1] Value associated with the key -- @return[2] The given default if the option is not set function ix.option.Get(client, key, default) assert(IsValid(client) and client:IsPlayer(), "expected valid player for argument #1") local option = ix.option.stored[key] if (option) then local clientOptions = ix.option.clients[client:SteamID64()] if (clientOptions) then local clientOption = clientOptions[key] if (clientOption != nil) then return clientOption end end return option.default end return default end -- sent whenever a client's networked option has changed net.Receive("ixOptionSet", function(length, client) local key = net.ReadString() local value = net.ReadType() local steamID = client:SteamID64() local option = ix.option.stored[key] if (option) then ix.option.clients[steamID] = ix.option.clients[steamID] or {} ix.option.clients[steamID][key] = value else ErrorNoHalt(string.format( "'%s' attempted to set option with invalid key '%s'\n", tostring(client) .. client:SteamID(), key )) end end) -- sent on first load to sync all networked option values net.Receive("ixOptionSync", function(length, client) local indices = net.ReadUInt(8) local data = {} for _ = 1, indices do data[net.ReadString()] = net.ReadType() end local steamID = client:SteamID64() ix.option.clients[steamID] = ix.option.clients[steamID] or {} for k, v in pairs(data) do local option = ix.option.stored[k] if (option) then ix.option.clients[steamID][k] = v else return ErrorNoHalt(string.format( "'%s' attempted to sync option with invalid key '%s'\n", tostring(client) .. client:SteamID(), k )) end end end) end ================================================ FILE: gamemode/core/libs/sh_player.lua ================================================ local playerMeta = FindMetaTable("Player") -- ixData information for the player. do if (SERVER) then function playerMeta:GetData(key, default) if (key == true) then return self.ixData end local data = self.ixData and self.ixData[key] if (data == nil) then return default else return data end end else function playerMeta:GetData(key, default) local data = ix.localData and ix.localData[key] if (data == nil) then return default else return data end end net.Receive("ixDataSync", function() ix.localData = net.ReadTable() ix.playTime = net.ReadUInt(32) end) net.Receive("ixData", function() ix.localData = ix.localData or {} ix.localData[net.ReadString()] = net.ReadType() end) end end -- Whitelist networking information here. do function playerMeta:HasWhitelist(faction) local data = ix.faction.indices[faction] if (data) then if (data.isDefault) then return true end local ixData = self:GetData("whitelists", {}) return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false end return false end function playerMeta:GetItems() local char = self:GetCharacter() if (char) then local inv = char:GetInventory() if (inv) then return inv:GetItems() end end end function playerMeta:GetClassData() local char = self:GetCharacter() if (char) then local class = char:GetClass() if (class) then local classData = ix.class.list[class] return classData end end end end do if (SERVER) then util.AddNetworkString("PlayerModelChanged") util.AddNetworkString("PlayerSelectWeapon") local entityMeta = FindMetaTable("Entity") entityMeta.ixSetModel = entityMeta.ixSetModel or entityMeta.SetModel playerMeta.ixSelectWeapon = playerMeta.ixSelectWeapon or playerMeta.SelectWeapon function entityMeta:SetModel(model) local oldModel = self:GetModel() if (self:IsPlayer()) then hook.Run("PlayerModelChanged", self, model, oldModel) net.Start("PlayerModelChanged") net.WriteEntity(self) net.WriteString(model) net.WriteString(oldModel) net.Broadcast() end return self:ixSetModel(model) end function playerMeta:SelectWeapon(className) net.Start("PlayerSelectWeapon") net.WriteEntity(self) net.WriteString(className) net.Broadcast() return self:ixSelectWeapon(className) end else net.Receive("PlayerModelChanged", function(length) hook.Run("PlayerModelChanged", net.ReadEntity(), net.ReadString(), net.ReadString()) end) net.Receive("PlayerSelectWeapon", function(length) local client = net.ReadEntity() local className = net.ReadString() if (!IsValid(client)) then hook.Run("PlayerWeaponChanged", client, NULL) return end for _, v in ipairs(client:GetWeapons()) do if (v:GetClass() == className) then hook.Run("PlayerWeaponChanged", client, v) break end end end) end end ================================================ FILE: gamemode/core/libs/sh_plugin.lua ================================================ ix.plugin = ix.plugin or {} ix.plugin.list = ix.plugin.list or {} ix.plugin.unloaded = ix.plugin.unloaded or {} ix.util.Include("helix/gamemode/core/meta/sh_tool.lua") -- luacheck: globals HOOKS_CACHE HOOKS_CACHE = {} function ix.plugin.Load(uniqueID, path, isSingleFile, variable) if (hook.Run("PluginShouldLoad", uniqueID) == false) then return end variable = variable or "PLUGIN" -- Plugins within plugins situation? local oldPlugin = PLUGIN local PLUGIN = { folder = path, plugin = oldPlugin, uniqueID = uniqueID, name = "Unknown", description = "Description not available", author = "Anonymous" } if (uniqueID == "schema") then if (Schema) then PLUGIN = Schema end variable = "Schema" PLUGIN.folder = engine.ActiveGamemode() elseif (ix.plugin.list[uniqueID]) then PLUGIN = ix.plugin.list[uniqueID] end _G[variable] = PLUGIN PLUGIN.loading = true if (!isSingleFile) then ix.lang.LoadFromDir(path.."/languages") ix.util.IncludeDir(path.."/libs", true) ix.attributes.LoadFromDir(path.."/attributes") ix.faction.LoadFromDir(path.."/factions") ix.class.LoadFromDir(path.."/classes") ix.item.LoadFromDir(path.."/items") ix.plugin.LoadFromDir(path.."/plugins") ix.util.IncludeDir(path.."/derma", true) ix.plugin.LoadEntities(path.."/entities") hook.Run("DoPluginIncludes", path, PLUGIN) end ix.util.Include(isSingleFile and path or path.."/sh_"..variable:lower()..".lua", "shared") PLUGIN.loading = false local uniqueID2 = uniqueID if (uniqueID2 == "schema") then uniqueID2 = PLUGIN.name end function PLUGIN:SetData(value, global, ignoreMap) ix.data.Set(uniqueID2, value, global, ignoreMap) end function PLUGIN:GetData(default, global, ignoreMap, refresh) return ix.data.Get(uniqueID2, default, global, ignoreMap, refresh) or {} end hook.Run("PluginLoaded", uniqueID, PLUGIN) if (uniqueID != "schema") then PLUGIN.name = PLUGIN.name or "Unknown" PLUGIN.description = PLUGIN.description or "No description available." for k, v in pairs(PLUGIN) do if (isfunction(v)) then HOOKS_CACHE[k] = HOOKS_CACHE[k] or {} HOOKS_CACHE[k][PLUGIN] = v end end ix.plugin.list[uniqueID] = PLUGIN _G[variable] = oldPlugin end if (PLUGIN.OnLoaded) then PLUGIN:OnLoaded() end end function ix.plugin.GetHook(pluginName, hookName) local h = HOOKS_CACHE[hookName] if (h) then local p = ix.plugin.list[pluginName] if (p) then return h[p] end end return end function ix.plugin.LoadEntities(path) local bLoadedTools local files, folders local function IncludeFiles(path2, bClientOnly) if (SERVER and !bClientOnly) then if (file.Exists(path2.."init.lua", "LUA")) then ix.util.Include(path2.."init.lua", "server") elseif (file.Exists(path2.."shared.lua", "LUA")) then ix.util.Include(path2.."shared.lua") end if (file.Exists(path2.."cl_init.lua", "LUA")) then ix.util.Include(path2.."cl_init.lua", "client") end elseif (file.Exists(path2.."cl_init.lua", "LUA")) then ix.util.Include(path2.."cl_init.lua", "client") elseif (file.Exists(path2.."shared.lua", "LUA")) then ix.util.Include(path2.."shared.lua") end end local function HandleEntityInclusion(folder, variable, register, default, clientOnly, create, complete) files, folders = file.Find(path.."/"..folder.."/*", "LUA") default = default or {} for _, v in ipairs(folders) do local path2 = path.."/"..folder.."/"..v.."/" v = ix.util.StripRealmPrefix(v) _G[variable] = table.Copy(default) if (!isfunction(create)) then _G[variable].ClassName = v else create(v) end IncludeFiles(path2, clientOnly) if (clientOnly) then if (CLIENT) then register(_G[variable], v) end else register(_G[variable], v) end if (isfunction(complete)) then complete(_G[variable]) end _G[variable] = nil end for _, v in ipairs(files) do local niceName = ix.util.StripRealmPrefix(string.StripExtension(v)) _G[variable] = table.Copy(default) if (!isfunction(create)) then _G[variable].ClassName = niceName else create(niceName) end ix.util.Include(path.."/"..folder.."/"..v, clientOnly and "client" or "shared") if (clientOnly) then if (CLIENT) then register(_G[variable], niceName) end else register(_G[variable], niceName) end if (isfunction(complete)) then complete(_G[variable]) end _G[variable] = nil end end local function RegisterTool(tool, className) local gmodTool = weapons.GetStored("gmod_tool") if (className:sub(1, 3) == "sh_") then className = className:sub(4) end if (gmodTool) then gmodTool.Tool[className] = tool else -- this should never happen ErrorNoHalt(string.format("attempted to register tool '%s' with invalid gmod_tool weapon", className)) end bLoadedTools = true end -- Include entities. HandleEntityInclusion("entities", "ENT", scripted_ents.Register, { Type = "anim", Base = "base_gmodentity", Spawnable = true }, false, nil, function(ent) if (SERVER and ent.Holdable == true) then ix.allowedHoldableClasses[ent.ClassName] = true end end) -- Include weapons. HandleEntityInclusion("weapons", "SWEP", weapons.Register, { Primary = {}, Secondary = {}, Base = "weapon_base" }) HandleEntityInclusion("tools", "TOOL", RegisterTool, {}, false, function(className) if (className:sub(1, 3) == "sh_") then className = className:sub(4) end TOOL = ix.meta.tool:Create() TOOL.Mode = className TOOL:CreateConVars() end) -- Include effects. HandleEntityInclusion("effects", "EFFECT", effects and effects.Register, nil, true) -- only reload spawn menu if any new tools were registered if (CLIENT and bLoadedTools) then RunConsoleCommand("spawnmenu_reload") end end function ix.plugin.Initialize() if SERVER then ix.plugin.unloaded = ix.data.Get("unloaded", {}, true, true) end ix.plugin.LoadFromDir("helix/plugins") ix.plugin.Load("schema", engine.ActiveGamemode().."/schema") hook.Run("InitializedSchema") ix.plugin.LoadFromDir(engine.ActiveGamemode().."/plugins") hook.Run("InitializedPlugins") end function ix.plugin.Get(identifier) return ix.plugin.list[identifier] end function ix.plugin.LoadFromDir(directory) local files, folders = file.Find(directory.."/*", "LUA") for _, v in ipairs(folders) do ix.plugin.Load(v, directory.."/"..v) end for _, v in ipairs(files) do ix.plugin.Load(string.StripExtension(v), directory.."/"..v, true) end end function ix.plugin.SetUnloaded(uniqueID, state, bNoSave) local plugin = ix.plugin.list[uniqueID] if (state) then if (plugin and plugin.OnUnload) then plugin:OnUnload() end ix.plugin.unloaded[uniqueID] = true elseif (ix.plugin.unloaded[uniqueID]) then ix.plugin.unloaded[uniqueID] = false else return false end if (SERVER and !bNoSave) then local status if (state) then status = true end local unloaded = ix.data.Get("unloaded", {}, true, true) unloaded[uniqueID] = status ix.data.Set("unloaded", unloaded, true, true) end if (state) then hook.Run("PluginUnloaded", uniqueID) end return true end if (SERVER) then --- Runs the `LoadData` and `PostLoadData` hooks for the gamemode, schema, and plugins. Any plugins that error during the -- hook will have their `SaveData` and `PostLoadData` hooks removed to prevent them from saving junk data. -- @internal -- @realm server function ix.plugin.RunLoadData() local errors = hook.SafeRun("LoadData") -- remove the SaveData and PostLoadData hooks for any plugins that error during LoadData since they would probably be -- saving bad data. this doesn't prevent plugins from saving data via other means, but there's only so much we can do for _, v in pairs(errors or {}) do if (v.plugin) then local plugin = ix.plugin.Get(v.plugin) if (plugin) then local saveDataHooks = HOOKS_CACHE["SaveData"] or {} saveDataHooks[plugin] = nil local postLoadDataHooks = HOOKS_CACHE["PostLoadData"] or {} postLoadDataHooks[plugin] = nil end end end hook.Run("PostLoadData") end end do -- luacheck: globals hook hook.ixCall = hook.ixCall or hook.Call function hook.Call(name, gm, ...) local cache = HOOKS_CACHE[name] if (cache) then for k, v in pairs(cache) do local a, b, c, d, e, f = v(k, ...) if (a != nil) then return a, b, c, d, e, f end end end if (Schema and Schema[name]) then local a, b, c, d, e, f = Schema[name](Schema, ...) if (a != nil) then return a, b, c, d, e, f end end return hook.ixCall(name, gm, ...) end --- Runs the given hook in a protected call so that the calling function will continue executing even if any errors occur -- while running the hook. This function is much more expensive to call than `hook.Run`, so you should avoid using it unless -- you absolutely need to avoid errors from stopping the execution of your function. -- @internal -- @realm shared -- @string name Name of the hook to run -- @param ... Arguments to pass to the hook functions -- @treturn[1] table Table of error data if an error occurred while running -- @treturn[1] ... Any arguments returned by the hook functions -- @usage local errors, bCanSpray = hook.SafeRun("PlayerSpray", Entity(1)) -- if (!errors) then -- -- do stuff with bCanSpray -- else -- PrintTable(errors) -- end function hook.SafeRun(name, ...) local errors = {} local gm = gmod and gmod.GetGamemode() or nil local cache = HOOKS_CACHE[name] if (cache) then for k, v in pairs(cache) do local bSuccess, a, b, c, d, e, f = pcall(v, k, ...) if (bSuccess) then if (a != nil) then return errors, a, b, c, d, e, f end else ErrorNoHalt(string.format("[Helix] hook.SafeRun error for plugin hook \"%s:%s\":\n\t%s\n%s\n", tostring(k and k.uniqueID or nil), tostring(name), tostring(a), debug.traceback())) errors[#errors + 1] = { name = name, plugin = k and k.uniqueID or nil, errorMessage = tostring(a) } end end end if (Schema and Schema[name]) then local bSuccess, a, b, c, d, e, f = pcall(Schema[name], Schema, ...) if (bSuccess) then if (a != nil) then return errors, a, b, c, d, e, f end else ErrorNoHalt(string.format("[Helix] hook.SafeRun error for schema hook \"%s\":\n\t%s\n%s\n", tostring(name), tostring(a), debug.traceback())) errors[#errors + 1] = { name = name, schema = Schema.name, errorMessage = tostring(a) } end end local bSuccess, a, b, c, d, e, f = pcall(hook.ixCall, name, gm, ...) if (bSuccess) then return errors, a, b, c, d, e, f else ErrorNoHalt(string.format("[Helix] hook.SafeRun error for gamemode hook \"%s\":\n\t%s\n%s\n", tostring(name), tostring(a), debug.traceback())) errors[#errors + 1] = { name = name, gamemode = "gamemode", errorMessage = tostring(a) } return errors end end end ================================================ FILE: gamemode/core/libs/sh_storage.lua ================================================ --[[-- Player manipulation of inventories. This library provides an easy way for players to manipulate other inventories. The only functions that you should need are `ix.storage.Open` and `ix.storage.Close`. When opening an inventory as a storage item, it will display both the given inventory and the player's inventory in the player's UI, which allows them to drag items to and from the given inventory. Example usage: ix.storage.Open(client, inventory, { name = "Filing Cabinet", entity = ents.GetByIndex(3), bMultipleUsers = true, searchText = "Rummaging...", searchTime = 4 }) ]] -- @module ix.storage --- There are some parameters you can customize when opening an inventory as a storage object with `ix.storage.Open`. -- @realm server -- @table StorageInfoStructure -- @field[type=entity] entity Entity to "attach" the inventory to. This is used to provide a location for the inventory for -- things like making sure the player doesn't move too far away from the inventory, etc. This can also be a `player` object. -- @field[type=number,opt=inventory id] id The ID of the nventory. This defaults to the inventory passed into `ix.Storage.Open`. -- @field[type=string,opt="Storage"] name Title to display in the UI when the inventory is open. -- @field[type=boolean,opt=false] bMultipleUsers Whether or not multiple players are allowed to view this inventory at the -- same time. -- @field[type=number,opt=0] searchTime How long the player has to wait before the inventory is opened. -- @field[type=string,opt="@storageSearching"] text Text to display to the user while opening the inventory. If prefixed with -- `"@"`, it will display a language phrase. -- @field[type=function,opt] OnPlayerClose Called when a player who was accessing the inventory has closed it. The -- argument passed to the callback is the player who closed it. -- @field[type=table,opt={}] data Table of arbitrary data to send to the client when the inventory has been opened. ix.storage = ix.storage or {} if (SERVER) then util.AddNetworkString("ixStorageOpen") util.AddNetworkString("ixStorageClose") util.AddNetworkString("ixStorageExpired") util.AddNetworkString("ixStorageMoneyTake") util.AddNetworkString("ixStorageMoneyGive") util.AddNetworkString("ixStorageMoneyUpdate") --- Returns whether or not the given inventory has a storage context and is being looked at by other players. -- @realm server -- @inventory inventory Inventory to check -- @treturn bool Whether or not `inventory` is in use function ix.storage.InUse(inventory) if (inventory.storageInfo) then for _, v in pairs(inventory:GetReceivers()) do if (IsValid(v) and v:IsPlayer() and v != inventory.storageInfo.entity) then return true end end end return false end --- Returns whether or not an inventory is in use by a specific player. -- @realm server -- @inventory inventory Inventory to check -- @player client Player to check -- @treturn bool Whether or not the player is using the given `inventory` function ix.storage.InUseBy(inventory, client) if (inventory.storageInfo) then for _, v in pairs(inventory:GetReceivers()) do if (IsValid(v) and v:IsPlayer() and v == client) then return true end end end return false end --- Creates a storage context on the given inventory. -- @realm server -- @internal -- @inventory inventory Inventory to create a storage context for -- @tab info Information to store on the context function ix.storage.CreateContext(inventory, info) info = info or {} info.id = inventory:GetID() info.name = info.name or "Storage" info.entity = assert(IsValid(info.entity), "expected valid entity in info table") and info.entity info.bMultipleUsers = info.bMultipleUsers == nil and false or info.bMultipleUsers info.searchTime = tonumber(info.searchTime) or 0 info.searchText = info.searchText or "@storageSearching" info.data = info.data or {} inventory.storageInfo = info -- remove context from any bags this inventory might have for k, _ in inventory:Iter() do if (k.isBag and k:GetInventory()) then ix.storage.CreateContext(k:GetInventory(), table.Copy(info)) end end end --- Removes a storage context from an inventory if it exists. -- @realm server -- @internal -- @inventory inventory Inventory to remove a storage context from function ix.storage.RemoveContext(inventory) inventory.storageInfo = nil -- remove context from any bags this inventory might have for k, _ in inventory:Iter() do if (k.isBag and k:GetInventory()) then ix.storage.RemoveContext(k:GetInventory()) end end end --- Synchronizes an inventory with a storage context to the given client. -- @realm server -- @internal -- @player client Player to sync storage for -- @inventory inventory Inventory to sync storage for function ix.storage.Sync(client, inventory) local info = inventory.storageInfo -- we'll retrieve the money value as we're syncing because it may have changed while -- we were waiting for the timer to finish if (info.entity.GetMoney) then info.data.money = info.entity:GetMoney() elseif (info.entity:IsPlayer() and info.entity:GetCharacter()) then info.data.money = info.entity:GetCharacter():GetMoney() end -- bags are automatically sync'd when the owning inventory is sync'd inventory:Sync(client) net.Start("ixStorageOpen") net.WriteUInt(info.id, 32) net.WriteEntity(info.entity) net.WriteString(info.name) net.WriteTable(info.data) net.Send(client) end --- Adds a receiver to a given inventory with a storage context. -- @realm server -- @internal -- @player client Player to sync storage for -- @inventory inventory Inventory to sync storage for -- @bool bDontSync Whether or not to skip syncing the storage to the client. If this is `true`, the storage panel will not -- show up for the player function ix.storage.AddReceiver(client, inventory, bDontSync) local info = inventory.storageInfo if (info) then inventory:AddReceiver(client) client.ixOpenStorage = inventory -- update receivers for any bags this inventory might have for k, _ in inventory:Iter() do if (k.isBag and k:GetInventory()) then k:GetInventory():AddReceiver(client) end end if (isfunction(info.OnPlayerOpen)) then info.OnPlayerOpen(client) end if (!bDontSync) then ix.storage.Sync(client, inventory) end return true end return false end --- Removes a storage receiver and removes the context if there are no more receivers. -- @realm server -- @internal -- @player client Player to remove from receivers -- @inventory inventory Inventory with storage context to remove receiver from -- @bool bDontRemove Whether or not to skip removing the storage context if there are no more receivers function ix.storage.RemoveReceiver(client, inventory, bDontRemove) local info = inventory.storageInfo if (info) then inventory:RemoveReceiver(client) -- update receivers for any bags this inventory might have for k, _ in inventory:Iter() do if (k.isBag and k:GetInventory()) then k:GetInventory():RemoveReceiver(client) end end if (isfunction(info.OnPlayerClose)) then info.OnPlayerClose(client) end if (!bDontRemove and !ix.storage.InUse(inventory)) then ix.storage.RemoveContext(inventory) end client.ixOpenStorage = nil return true end return false end --- Makes a player open an inventory that they can interact with. This can be called multiple times on the same inventory, -- if the info passed allows for multiple users. -- @realm server -- @player client Player to open the inventory for -- @inventory inventory Inventory to open -- @tab info `StorageInfoStructure` describing the storage properties function ix.storage.Open(client, inventory, info) assert(IsValid(client) and client:IsPlayer(), "expected valid player") assert(type(inventory) == "table" and inventory:IsInstanceOf(ix.meta.inventory), "expected valid inventory") -- create storage context if one isn't already created if (!inventory.storageInfo) then info = info or {} ix.storage.CreateContext(inventory, info) end local storageInfo = inventory.storageInfo -- add the client to the list of receivers if we're allowed to have multiple users -- or if nobody else is occupying this inventory, otherwise nag the player if (storageInfo.bMultipleUsers or !ix.storage.InUse(inventory)) then ix.storage.AddReceiver(client, inventory, true) else client:NotifyLocalized("storageInUse") return end if (storageInfo.searchTime > 0) then client:SetAction(storageInfo.searchText, storageInfo.searchTime) client:DoStaredAction(storageInfo.entity, function() if (IsValid(client) and IsValid(storageInfo.entity) and inventory.storageInfo) then ix.storage.Sync(client, inventory) end end, storageInfo.searchTime, function() if (IsValid(client)) then ix.storage.RemoveReceiver(client, inventory) client:SetAction() end end) else ix.storage.Sync(client, inventory) end end --- Forcefully makes clients close this inventory if they have it open. -- @realm server -- @inventory inventory Inventory to close function ix.storage.Close(inventory) local receivers = inventory:GetReceivers() if (#receivers > 0) then net.Start("ixStorageExpired") net.WriteUInt(inventory.storageInfo.id, 32) net.Send(receivers) end ix.storage.RemoveContext(inventory) end net.Receive("ixStorageClose", function(length, client) local inventory = client.ixOpenStorage if (inventory) then ix.storage.RemoveReceiver(client, inventory) end end) net.Receive("ixStorageMoneyTake", function(length, client) if (CurTime() < (client.ixStorageMoneyTimer or 0)) then return end local character = client:GetCharacter() if (!character) then return end local storageID = net.ReadUInt(32) local amount = net.ReadUInt(32) local inventory = client.ixOpenStorage if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then return end local entity = inventory.storageInfo.entity if (!IsValid(entity) or (!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or (entity:IsPlayer() and !entity:GetCharacter())) then return end entity = entity:IsPlayer() and entity:GetCharacter() or entity amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, entity:GetMoney()) if (amount == 0) then return end character:SetMoney(character:GetMoney() + amount) local total = entity:GetMoney() - amount entity:SetMoney(total) net.Start("ixStorageMoneyUpdate") net.WriteUInt(storageID, 32) net.WriteUInt(total, 32) net.Send(inventory:GetReceivers()) ix.log.Add(client, "storageMoneyTake", entity, amount, total) client.ixStorageMoneyTimer = CurTime() + 0.5 end) net.Receive("ixStorageMoneyGive", function(length, client) if (CurTime() < (client.ixStorageMoneyTimer or 0)) then return end local character = client:GetCharacter() if (!character) then return end local storageID = net.ReadUInt(32) local amount = net.ReadUInt(32) local inventory = client.ixOpenStorage if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then return end local entity = inventory.storageInfo.entity if (!IsValid(entity) or (!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or (entity:IsPlayer() and !entity:GetCharacter())) then return end entity = entity:IsPlayer() and entity:GetCharacter() or entity amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, character:GetMoney()) if (amount == 0) then return end character:SetMoney(character:GetMoney() - amount) local total = entity:GetMoney() + amount entity:SetMoney(total) net.Start("ixStorageMoneyUpdate") net.WriteUInt(storageID, 32) net.WriteUInt(total, 32) net.Send(inventory:GetReceivers()) ix.log.Add(client, "storageMoneyGive", entity, amount, total) client.ixStorageMoneyTimer = CurTime() + 0.5 end) else net.Receive("ixStorageOpen", function() if (IsValid(ix.gui.menu)) then net.Start("ixStorageClose") net.SendToServer() return end local id = net.ReadUInt(32) local entity = net.ReadEntity() local name = net.ReadString() local data = net.ReadTable() local inventory = ix.item.inventories[id] if (IsValid(entity) and inventory and inventory.slots) then local localInventory = LocalPlayer():GetCharacter():GetInventory() local panel = vgui.Create("ixStorageView") if (localInventory) then panel:SetLocalInventory(localInventory) end panel:SetStorageID(id) panel:SetStorageTitle(name) panel:SetStorageInventory(inventory) if (data.money) then if (localInventory) then panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney()) end panel:SetStorageMoney(data.money) end end end) net.Receive("ixStorageExpired", function() if (IsValid(ix.gui.openedStorage)) then ix.gui.openedStorage:Remove() end local id = net.ReadUInt(32) if (id != 0) then ix.item.inventories[id] = nil end end) net.Receive("ixStorageMoneyUpdate", function() local storageID = net.ReadUInt(32) local amount = net.ReadUInt(32) local panel = ix.gui.openedStorage if (!IsValid(panel) or panel:GetStorageID() != storageID) then return end panel:SetStorageMoney(amount) panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney()) end) end ================================================ FILE: gamemode/core/libs/sv_database.lua ================================================ ix.db = ix.db or { schema = {}, schemaQueue = {}, type = { -- TODO: more specific types, lengths, and defaults -- i.e INT(11) UNSIGNED, SMALLINT(4), LONGTEXT, VARCHAR(350), NOT NULL, DEFAULT NULL, etc [ix.type.string] = "VARCHAR(255)", [ix.type.text] = "TEXT", [ix.type.number] = "INT(11)", [ix.type.steamid] = "VARCHAR(20)", [ix.type.bool] = "TINYINT(1)" } } ix.db.config = ix.config.server.database or {} function ix.db.Connect() ix.db.config.adapter = ix.db.config.adapter or "sqlite" local dbmodule = ix.db.config.adapter local hostname = ix.db.config.hostname local username = ix.db.config.username local password = ix.db.config.password local database = ix.db.config.database local port = ix.db.config.port mysql:SetModule(dbmodule) mysql:Connect(hostname, username, password, database, port) end function ix.db.AddToSchema(schemaType, field, fieldType) if (!ix.db.type[fieldType]) then error(string.format("attempted to add field in schema with invalid type '%s'", fieldType)) return end if (!mysql:IsConnected() or !ix.db.schema[schemaType]) then ix.db.schemaQueue[#ix.db.schemaQueue + 1] = {schemaType, field, fieldType} return end ix.db.InsertSchema(schemaType, field, fieldType) end -- this is only ever used internally function ix.db.InsertSchema(schemaType, field, fieldType) local schema = ix.db.schema[schemaType] if (!schema) then error(string.format("attempted to insert into schema with invalid schema type '%s'", schemaType)) return end if (!schema[field]) then schema[field] = true local query = mysql:Update("ix_schema") query:Update("columns", util.TableToJSON(schema)) query:Where("table", schemaType) query:Execute() query = mysql:Alter(schemaType) query:Add(field, ix.db.type[fieldType]) query:Execute() end end function ix.db.LoadTables() local query query = mysql:Create("ix_schema") query:Create("table", "VARCHAR(64) NOT NULL") query:Create("columns", "TEXT NOT NULL") query:PrimaryKey("table") query:Execute() -- table structure will be populated with more fields when vars -- are registered using ix.char.RegisterVar query = mysql:Create("ix_characters") query:Create("id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT") query:PrimaryKey("id") query:Execute() query = mysql:Create("ix_inventories") query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT") query:Create("character_id", "INT(11) UNSIGNED NOT NULL") query:Create("inventory_type", "VARCHAR(150) DEFAULT NULL") query:PrimaryKey("inventory_id") query:Execute() query = mysql:Create("ix_items") query:Create("item_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT") query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL") query:Create("unique_id", "VARCHAR(60) NOT NULL") query:Create("character_id", "INT(11) UNSIGNED DEFAULT NULL") query:Create("player_id", "VARCHAR(20) DEFAULT NULL") query:Create("data", "TEXT DEFAULT NULL") query:Create("x", "SMALLINT(4) NOT NULL") query:Create("y", "SMALLINT(4) NOT NULL") query:PrimaryKey("item_id") query:Execute() query = mysql:Create("ix_players") query:Create("steamid", "VARCHAR(20) NOT NULL") query:Create("steam_name", "VARCHAR(32) NOT NULL") query:Create("play_time", "INT(11) UNSIGNED DEFAULT NULL") query:Create("address", "VARCHAR(15) DEFAULT NULL") query:Create("last_join_time", "INT(11) UNSIGNED DEFAULT NULL") query:Create("data", "TEXT") query:PrimaryKey("steamid") query:Execute() -- populate schema table if rows don't exist query = mysql:InsertIgnore("ix_schema") query:Insert("table", "ix_characters") query:Insert("columns", util.TableToJSON({})) query:Execute() -- load schema from database query = mysql:Select("ix_schema") query:Callback(function(result) if (!istable(result)) then return end for _, v in pairs(result) do ix.db.schema[v.table] = util.JSONToTable(v.columns) end -- update schema if needed for i = 1, #ix.db.schemaQueue do local entry = ix.db.schemaQueue[i] ix.db.InsertSchema(entry[1], entry[2], entry[3]) end end) query:Execute() end function ix.db.WipeTables(callback) local query query = mysql:Drop("ix_schema") query:Execute() query = mysql:Drop("ix_characters") query:Execute() query = mysql:Drop("ix_inventories") query:Execute() query = mysql:Drop("ix_items") query:Execute() query = mysql:Drop("ix_players") query:Callback(callback) query:Execute() end hook.Add("InitPostEntity", "ixDatabaseConnect", function() -- Connect to the database using SQLite, mysqoo, or tmysql4. ix.db.Connect() end) local resetCalled = 0 concommand.Add("ix_wipedb", function(client, cmd, arguments) -- can only be ran through the server's console if (!IsValid(client)) then if (resetCalled < RealTime()) then resetCalled = RealTime() + 3 MsgC(Color(255, 0, 0), "[Helix] WIPING THE DATABASE WILL PERMENANTLY REMOVE ALL PLAYER, CHARACTER, ITEM, AND INVENTORY DATA.\n") MsgC(Color(255, 0, 0), "[Helix] THE SERVER WILL RESTART TO APPLY THESE CHANGES WHEN COMPLETED.\n") MsgC(Color(255, 0, 0), "[Helix] TO CONFIRM DATABASE RESET, RUN 'ix_wipedb' AGAIN WITHIN 3 SECONDS.\n") else resetCalled = 0 MsgC(Color(255, 0, 0), "[Helix] DATABASE WIPE IN PROGRESS...\n") hook.Run("OnWipeTables") ix.db.WipeTables(function() MsgC(Color(255, 255, 0), "[Helix] DATABASE WIPE COMPLETED!\n") RunConsoleCommand("changelevel", game.GetMap()) end) end end end) ================================================ FILE: gamemode/core/libs/sv_networking.lua ================================================ -- @module ix.net local entityMeta = FindMetaTable("Entity") local playerMeta = FindMetaTable("Player") ix.net = ix.net or {} ix.net.list = ix.net.list or {} ix.net.locals = ix.net.locals or {} ix.net.globals = ix.net.globals or {} util.AddNetworkString("ixGlobalVarSet") util.AddNetworkString("ixLocalVarSet") util.AddNetworkString("ixNetVarSet") util.AddNetworkString("ixNetVarDelete") -- Check if there is an attempt to send a function. Can't send those. local function CheckBadType(name, object) if (isfunction(object)) then ErrorNoHalt("Net var '" .. name .. "' contains a bad object type!") return true elseif (istable(object)) then for k, v in pairs(object) do -- Check both the key and the value for tables, and has recursion. if (CheckBadType(name, k) or CheckBadType(name, v)) then return true end end end end function GetNetVar(key, default) -- luacheck: globals GetNetVar local value = ix.net.globals[key] return value != nil and value or default end function SetNetVar(key, value, receiver) -- luacheck: globals SetNetVar if (CheckBadType(key, value)) then return end if (GetNetVar(key) == value and !istable(value)) then return end ix.net.globals[key] = value net.Start("ixGlobalVarSet") net.WriteString(key) net.WriteType(value) if (receiver == nil) then net.Broadcast() else net.Send(receiver) end end --- Player networked variable functions -- @classmod Player --- Synchronizes networked variables to the client. -- @realm server -- @internal function playerMeta:SyncVars() for k, v in pairs(ix.net.globals) do net.Start("ixGlobalVarSet") net.WriteString(k) net.WriteType(v) net.Send(self) end for k, v in pairs(ix.net.locals[self] or {}) do net.Start("ixLocalVarSet") net.WriteString(k) net.WriteType(v) net.Send(self) end for entity, data in pairs(ix.net.list) do if (IsValid(entity)) then local index = entity:EntIndex() for k, v in pairs(data) do net.Start("ixNetVarSet") net.WriteUInt(index, 16) net.WriteString(k) net.WriteType(v) net.Send(self) end end end end --- Retrieves a local networked variable. If it is not set, it'll return the default that you've specified. -- Locally networked variables can only be retrieved from the owning player when used from the client. -- @realm shared -- @string key Identifier of the local variable -- @param default Default value to return if the local variable is not set -- @return Value associated with the key, or the default that was given if it doesn't exist -- @usage print(client:GetLocalVar("secret")) -- > 12345678 -- @see SetLocalVar function playerMeta:GetLocalVar(key, default) if (ix.net.locals[self] and ix.net.locals[self][key] != nil) then return ix.net.locals[self][key] end return default end --- Sets the value of a local networked variable. -- @realm server -- @string key Identifier of the local variable -- @param value New value to assign to the local variable -- @usage client:SetLocalVar("secret", 12345678) -- @see GetLocalVar function playerMeta:SetLocalVar(key, value) if (CheckBadType(key, value)) then return end ix.net.locals[self] = ix.net.locals[self] or {} ix.net.locals[self][key] = value net.Start("ixLocalVarSet") net.WriteString(key) net.WriteType(value) net.Send(self) end --- Entity networked variable functions -- @classmod Entity --- Retrieves a networked variable. If it is not set, it'll return the default that you've specified. -- @realm shared -- @string key Identifier of the networked variable -- @param default Default value to return if the networked variable is not set -- @return Value associated with the key, or the default that was given if it doesn't exist -- @usage print(client:GetNetVar("example")) -- > Hello World! -- @see SetNetVar function entityMeta:GetNetVar(key, default) if (ix.net.list[self] and ix.net.list[self][key] != nil) then return ix.net.list[self][key] end return default end --- Sets the value of a networked variable. -- @realm server -- @string key Identifier of the networked variable -- @param value New value to assign to the networked variable -- @tab[opt=nil] receiver The players to send the networked variable to -- @usage client:SetNetVar("example", "Hello World!") -- @see GetNetVar function entityMeta:SetNetVar(key, value, receiver) if (CheckBadType(key, value)) then return end ix.net.list[self] = ix.net.list[self] or {} if (ix.net.list[self][key] != value) then ix.net.list[self][key] = value end self:SendNetVar(key, receiver) end --- Sends a networked variable. -- @realm server -- @internal -- @string key Identifier of the networked variable -- @tab[opt=nil] receiver The players to send the networked variable to function entityMeta:SendNetVar(key, receiver) net.Start("ixNetVarSet") net.WriteUInt(self:EntIndex(), 16) net.WriteString(key) net.WriteType(ix.net.list[self] and ix.net.list[self][key]) if (receiver == nil) then net.Broadcast() else net.Send(receiver) end end --- Clears all of the networked variables. -- @realm server -- @internal -- @tab[opt=nil] receiver The players to clear the networked variable for function entityMeta:ClearNetVars(receiver) ix.net.list[self] = nil ix.net.locals[self] = nil net.Start("ixNetVarDelete") net.WriteUInt(self:EntIndex(), 16) if (receiver == nil) then net.Broadcast() else net.Send(receiver) end end ================================================ FILE: gamemode/core/libs/sv_player.lua ================================================ local playerMeta = FindMetaTable("Player") -- Player data (outside of characters) handling. do util.AddNetworkString("ixData") util.AddNetworkString("ixDataSync") function playerMeta:LoadData(callback) local name = self:SteamName() local steamID64 = self:SteamID64() local timestamp = math.floor(os.time()) local ip = self:IPAddress():match("%d+%.%d+%.%d+%.%d+") local query = mysql:Select("ix_players") query:Select("data") query:Select("play_time") query:Where("steamid", steamID64) query:Callback(function(result) if (IsValid(self) and istable(result) and #result > 0 and result[1].data) then local updateQuery = mysql:Update("ix_players") updateQuery:Update("last_join_time", timestamp) updateQuery:Update("address", ip) updateQuery:Where("steamid", steamID64) updateQuery:Execute() self.ixPlayTime = tonumber(result[1].play_time) or 0 self.ixData = util.JSONToTable(result[1].data) if (callback) then callback(self.ixData) end else local insertQuery = mysql:Insert("ix_players") insertQuery:Insert("steamid", steamID64) insertQuery:Insert("steam_name", name) insertQuery:Insert("play_time", 0) insertQuery:Insert("address", ip) insertQuery:Insert("last_join_time", timestamp) insertQuery:Insert("data", util.TableToJSON({})) insertQuery:Execute() if (callback) then callback({}) end end end) query:Execute() end function playerMeta:SaveData() if (self:IsBot()) then return end local name = self:SteamName() local steamID64 = self:SteamID64() local query = mysql:Update("ix_players") query:Update("steam_name", name) query:Update("play_time", math.floor((self.ixPlayTime or 0) + (RealTime() - (self.ixJoinTime or RealTime() - 1)))) query:Update("data", util.TableToJSON(self.ixData)) query:Where("steamid", steamID64) query:Execute() end function playerMeta:SetData(key, value, bNoNetworking) self.ixData = self.ixData or {} self.ixData[key] = value if (!bNoNetworking) then net.Start("ixData") net.WriteString(key) net.WriteType(value) net.Send(self) end end end -- Whitelisting information for the player. do function playerMeta:SetWhitelisted(faction, whitelisted) if (!whitelisted) then whitelisted = nil end local data = ix.faction.indices[faction] if (data) then local whitelists = self:GetData("whitelists", {}) whitelists[Schema.folder] = whitelists[Schema.folder] or {} whitelists[Schema.folder][data.uniqueID] = whitelisted and true or nil self:SetData("whitelists", whitelists) self:SaveData() return true end return false end end do playerMeta.ixGive = playerMeta.ixGive or playerMeta.Give function playerMeta:Give(className, bNoAmmo) local weapon self.ixWeaponGive = true weapon = self:ixGive(className, bNoAmmo) self.ixWeaponGive = nil return weapon end end ================================================ FILE: gamemode/core/libs/thirdparty/cl_ikon.lua ================================================ --[[ BLACK TEA ICON LIBRARY FOR NUTSCRIPT 1.1 The MIT License (MIT) Copyright (c) 2017, Kyu Yeon Lee(Black Tea Za rebel1324) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and thispermission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. TL;DR: https://tldrlegal.com/license/mit-license OK - Commercial Use Modify Distribute Sublicense Private Use NOT OK - Hold Liable MUST - Include Copyright Include License ]]-- --[[ Default Tables. ]]-- ikon = ikon or {} ikon.cache = ikon.cache or {} ikon.requestList = ikon.requestList or {} ikon.dev = false ikon.maxSize = 8 -- 8x8 (512^2) is max icon size. IKON_BUSY = 1 IKON_PROCESSING = 0 IKON_SOMETHINGWRONG = -1 local schemaName = schemaName or (Schema and Schema.folder) --[[ Initialize hooks and RT Screens. returns nothing ]]-- function ikon:init() if (self.dev) then hook.Add("HUDPaint", "ikon_dev2", ikon.showResult) else hook.Remove("HUDPaint", "ikon_dev2") end --[[ Being good at gmod is knowing all of stinky hacks - Black Tea (2017) ]]-- ikon.haloAdd = ikon.haloAdd or halo.Add function halo.Add(...) if (ikon.rendering != true) then ikon.haloAdd(...) end end ikon.haloRender = ikon.haloRender or halo.Render function halo.Render(...) if (ikon.rendering != true) then ikon.haloRender(...) end end file.CreateDir("helix/icons") file.CreateDir("helix/icons/" .. schemaName) end --[[ IKON Library Essential Material/Texture Declare ]]-- local TEXTURE_FLAGS_CLAMP_S = 0x0004 local TEXTURE_FLAGS_CLAMP_T = 0x0008 ikon.max = ikon.maxSize * 64 ikon.RT = GetRenderTargetEx("ixIconRendered", ikon.max, ikon.max, RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SHARED, bit.bor(TEXTURE_FLAGS_CLAMP_S, TEXTURE_FLAGS_CLAMP_T), CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGBA8888 ) local tex_effect = GetRenderTarget("ixIconRenderedOutline", ikon.max, ikon.max) local mat_outline = CreateMaterial("ixIconRenderedTemp", "UnlitGeneric", { ["$basetexture"] = tex_effect:GetName(), ["$translucent"] = 1 }) local lightPositions = { BOX_TOP = Color(255, 255, 255), BOX_FRONT = Color(255, 255, 255), } function ikon:renderHook() local entity = ikon.renderEntity if (halo.RenderedEntity() == entity) then return end local w, h = ikon.curWidth * 64, ikon.curHeight * 64 local x, y = 0, 0 local tab if (ikon.info) then tab = { origin = ikon.info.pos, angles = ikon.info.ang, fov = ikon.info.fov, outline = ikon.info.outline, outCol = ikon.info.outlineColor } if (!tab.origin and !tab.angles and !tab.fov) then table.Merge(tab, PositionSpawnIcon(entity, entity:GetPos(), true)) end else tab = PositionSpawnIcon(entity, entity:GetPos(), true) end -- Taking MDave's Tip xpcall(function() render.OverrideAlphaWriteEnable(true, true) -- some playermodel eyeballs will not render without this render.SetWriteDepthToDestAlpha(false) render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD) render.SuppressEngineLighting(true) render.Clear(0, 0, 0, 0, true, true) render.SetLightingOrigin(vector_origin) render.ResetModelLighting(200 / 255, 200 / 255, 200 / 255) render.SetColorModulation(1, 1, 1) for i = 0, 6 do local col = lightPositions[i] if (col) then render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255) end end if (tab.outline) then ix.util.ResetStencilValues() render.SetStencilEnable(true) render.SetStencilWriteMask(137) -- yeah random number to avoid confliction render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) render.SetStencilPassOperation(STENCILOPERATION_REPLACE) render.SetStencilFailOperation(STENCILOPERATION_REPLACE) end --[[ Add more effects on the Models! ]]-- if (ikon.info and ikon.info.drawHook) then ikon.info.drawHook(entity) end cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h) render.SetBlend(1) entity:DrawModel() cam.End3D() if (tab.outline) then render.PushRenderTarget(tex_effect) render.Clear(0, 0, 0, 0) render.ClearDepth() cam.Start2D() cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h) render.SetBlend(0) entity:DrawModel() render.SetStencilWriteMask(138) -- could you please? render.SetStencilTestMask(1) render.SetStencilReferenceValue(1) render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) render.SetStencilPassOperation(STENCILOPERATION_KEEP) render.SetStencilFailOperation(STENCILOPERATION_KEEP) cam.Start2D() surface.SetDrawColor(tab.outCol or color_white) surface.DrawRect(0, 0, ScrW(), ScrH()) cam.End2D() cam.End3D() cam.End2D() render.PopRenderTarget() render.SetBlend(1) render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NOTEQUAL) --[[ Thanks for Noiwex NxServ.eu ]]-- cam.Start2D() surface.SetMaterial(mat_outline) surface.DrawTexturedRectUV(-2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max) surface.DrawTexturedRectUV(2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max) surface.DrawTexturedRectUV(0, 2, w, h, 0, 0, w / ikon.max, h / ikon.max) surface.DrawTexturedRectUV(0, -2, w, h, 0, 0, w / ikon.max, h / ikon.max) cam.End2D() render.SetStencilEnable(false) end render.SuppressEngineLighting(false) render.SetWriteDepthToDestAlpha(true) render.OverrideAlphaWriteEnable(false) end, function(message) print(message) end) end function ikon:showResult() local x, y = ScrW() / 2, ScrH() / 2 local w, h = ikon.curWidth * 64, ikon.curHeight * 64 surface.SetDrawColor(255, 255, 255, 255) surface.DrawOutlinedRect(x, 0, w, h) surface.SetMaterial(mat_outline) surface.DrawTexturedRect(x, 0, w, h) end --[[ Renders the Icon with given arguments. returns nothing ]]-- function ikon:renderIcon(name, w, h, mdl, camInfo, updateCache) if (#ikon.requestList > 0) then return IKON_BUSY end if (ikon.requestList[name]) then return IKON_PROCESSING end if (!w or !h or !mdl) then return IKON_SOMETHINGWRONG end local capturedIcon ikon.curWidth = w or 1 ikon.curHeight = h or 1 ikon.renderModel = mdl if (camInfo) then ikon.info = camInfo end local w, h = ikon.curWidth * 64, ikon.curHeight * 64 local sw, sh = ScrW(), ScrH() if (ikon.renderModel) then if (!IsValid(ikon.renderEntity)) then ikon.renderEntity = ClientsideModel(ikon.renderModel, RENDERGROUP_BOTH) ikon.renderEntity:SetNoDraw(true) end end ikon.renderEntity:SetModel(ikon.renderModel) local bone = ikon.renderEntity:LookupBone("ValveBiped.Bip01_Head1") if (bone) then ikon.renderEntity:SetEyeTarget(ikon.renderEntity:GetBonePosition(bone) + ikon.renderEntity:GetForward() * 32) end local oldRT = render.GetRenderTarget() render.PushRenderTarget(ikon.RT) ikon.rendering = true ikon:renderHook() ikon.rendering = nil capturedIcon = render.Capture({ format = "png", alpha = true, x = 0, y = 0, w = w, h = h }) file.Write("helix/icons/" .. schemaName .. "/" .. name .. ".png", capturedIcon) ikon.info = nil render.PopRenderTarget() if (updateCache) then local materialID = tostring(os.time()) file.Write(materialID .. ".png", capturedIcon) timer.Simple(0, function() local material = Material("../data/".. materialID ..".png") ikon.cache[name] = material file.Delete(materialID .. ".png") end) end ikon.requestList[name] = nil return true end --[[ Gets rendered icon with given unique name. returns IMaterial ]]-- function ikon:GetIcon(name) if (ikon.cache[name]) then return ikon.cache[name] -- yeah return cache end if (file.Exists("helix/icons/" .. schemaName .. "/" .. name .. ".png", "DATA")) then ikon.cache[name] = Material("../data/helix/icons/" .. schemaName .. "/".. name ..".png") return ikon.cache[name] -- yeah return cache else return false -- retryd end end concommand.Add("ix_flushicon", function() local root = "helix/icons/" .. schemaName for _, v in ipairs(file.Find(root .. "/*.png", "DATA")) do file.Delete(root .. "/" .. v) end ikon.cache = {} end) hook.Add("InitializedSchema", "updatePath", function() schemaName = Schema.folder ikon:init() end) if (schemaName) then ikon:init() end ================================================ FILE: gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua ================================================ utf8_lc_uc = { ["a"] = "A", ["b"] = "B", ["c"] = "C", ["d"] = "D", ["e"] = "E", ["f"] = "F", ["g"] = "G", ["h"] = "H", ["i"] = "I", ["j"] = "J", ["k"] = "K", ["l"] = "L", ["m"] = "M", ["n"] = "N", ["o"] = "O", ["p"] = "P", ["q"] = "Q", ["r"] = "R", ["s"] = "S", ["t"] = "T", ["u"] = "U", ["v"] = "V", ["w"] = "W", ["x"] = "X", ["y"] = "Y", ["z"] = "Z", ["µ"] = "Μ", ["à"] = "À", ["á"] = "Á", ["â"] = "Â", ["ã"] = "Ã", ["ä"] = "Ä", ["å"] = "Å", ["æ"] = "Æ", ["ç"] = "Ç", ["è"] = "È", ["é"] = "É", ["ê"] = "Ê", ["ë"] = "Ë", ["ì"] = "Ì", ["í"] = "Í", ["î"] = "Î", ["ï"] = "Ï", ["ð"] = "Ð", ["ñ"] = "Ñ", ["ò"] = "Ò", ["ó"] = "Ó", ["ô"] = "Ô", ["õ"] = "Õ", ["ö"] = "Ö", ["ø"] = "Ø", ["ù"] = "Ù", ["ú"] = "Ú", ["û"] = "Û", ["ü"] = "Ü", ["ý"] = "Ý", ["þ"] = "Þ", ["ÿ"] = "Ÿ", ["ā"] = "Ā", ["ă"] = "Ă", ["ą"] = "Ą", ["ć"] = "Ć", ["ĉ"] = "Ĉ", ["ċ"] = "Ċ", ["č"] = "Č", ["ď"] = "Ď", ["đ"] = "Đ", ["ē"] = "Ē", ["ĕ"] = "Ĕ", ["ė"] = "Ė", ["ę"] = "Ę", ["ě"] = "Ě", ["ĝ"] = "Ĝ", ["ğ"] = "Ğ", ["ġ"] = "Ġ", ["ģ"] = "Ģ", ["ĥ"] = "Ĥ", ["ħ"] = "Ħ", ["ĩ"] = "Ĩ", ["ī"] = "Ī", ["ĭ"] = "Ĭ", ["į"] = "Į", ["ı"] = "I", ["ij"] = "IJ", ["ĵ"] = "Ĵ", ["ķ"] = "Ķ", ["ĺ"] = "Ĺ", ["ļ"] = "Ļ", ["ľ"] = "Ľ", ["ŀ"] = "Ŀ", ["ł"] = "Ł", ["ń"] = "Ń", ["ņ"] = "Ņ", ["ň"] = "Ň", ["ŋ"] = "Ŋ", ["ō"] = "Ō", ["ŏ"] = "Ŏ", ["ő"] = "Ő", ["œ"] = "Œ", ["ŕ"] = "Ŕ", ["ŗ"] = "Ŗ", ["ř"] = "Ř", ["ś"] = "Ś", ["ŝ"] = "Ŝ", ["ş"] = "Ş", ["š"] = "Š", ["ţ"] = "Ţ", ["ť"] = "Ť", ["ŧ"] = "Ŧ", ["ũ"] = "Ũ", ["ū"] = "Ū", ["ŭ"] = "Ŭ", ["ů"] = "Ů", ["ű"] = "Ű", ["ų"] = "Ų", ["ŵ"] = "Ŵ", ["ŷ"] = "Ŷ", ["ź"] = "Ź", ["ż"] = "Ż", ["ž"] = "Ž", ["ſ"] = "S", ["ƀ"] = "Ƀ", ["ƃ"] = "Ƃ", ["ƅ"] = "Ƅ", ["ƈ"] = "Ƈ", ["ƌ"] = "Ƌ", ["ƒ"] = "Ƒ", ["ƕ"] = "Ƕ", ["ƙ"] = "Ƙ", ["ƚ"] = "Ƚ", ["ƞ"] = "Ƞ", ["ơ"] = "Ơ", ["ƣ"] = "Ƣ", ["ƥ"] = "Ƥ", ["ƨ"] = "Ƨ", ["ƭ"] = "Ƭ", ["ư"] = "Ư", ["ƴ"] = "Ƴ", ["ƶ"] = "Ƶ", ["ƹ"] = "Ƹ", ["ƽ"] = "Ƽ", ["ƿ"] = "Ƿ", ["Dž"] = "DŽ", ["dž"] = "DŽ", ["Lj"] = "LJ", ["lj"] = "LJ", ["Nj"] = "NJ", ["nj"] = "NJ", ["ǎ"] = "Ǎ", ["ǐ"] = "Ǐ", ["ǒ"] = "Ǒ", ["ǔ"] = "Ǔ", ["ǖ"] = "Ǖ", ["ǘ"] = "Ǘ", ["ǚ"] = "Ǚ", ["ǜ"] = "Ǜ", ["ǝ"] = "Ǝ", ["ǟ"] = "Ǟ", ["ǡ"] = "Ǡ", ["ǣ"] = "Ǣ", ["ǥ"] = "Ǥ", ["ǧ"] = "Ǧ", ["ǩ"] = "Ǩ", ["ǫ"] = "Ǫ", ["ǭ"] = "Ǭ", ["ǯ"] = "Ǯ", ["Dz"] = "DZ", ["dz"] = "DZ", ["ǵ"] = "Ǵ", ["ǹ"] = "Ǹ", ["ǻ"] = "Ǻ", ["ǽ"] = "Ǽ", ["ǿ"] = "Ǿ", ["ȁ"] = "Ȁ", ["ȃ"] = "Ȃ", ["ȅ"] = "Ȅ", ["ȇ"] = "Ȇ", ["ȉ"] = "Ȉ", ["ȋ"] = "Ȋ", ["ȍ"] = "Ȍ", ["ȏ"] = "Ȏ", ["ȑ"] = "Ȑ", ["ȓ"] = "Ȓ", ["ȕ"] = "Ȕ", ["ȗ"] = "Ȗ", ["ș"] = "Ș", ["ț"] = "Ț", ["ȝ"] = "Ȝ", ["ȟ"] = "Ȟ", ["ȣ"] = "Ȣ", ["ȥ"] = "Ȥ", ["ȧ"] = "Ȧ", ["ȩ"] = "Ȩ", ["ȫ"] = "Ȫ", ["ȭ"] = "Ȭ", ["ȯ"] = "Ȯ", ["ȱ"] = "Ȱ", ["ȳ"] = "Ȳ", ["ȼ"] = "Ȼ", ["ɂ"] = "Ɂ", ["ɇ"] = "Ɇ", ["ɉ"] = "Ɉ", ["ɋ"] = "Ɋ", ["ɍ"] = "Ɍ", ["ɏ"] = "Ɏ", ["ɓ"] = "Ɓ", ["ɔ"] = "Ɔ", ["ɖ"] = "Ɖ", ["ɗ"] = "Ɗ", ["ə"] = "Ə", ["ɛ"] = "Ɛ", ["ɠ"] = "Ɠ", ["ɣ"] = "Ɣ", ["ɨ"] = "Ɨ", ["ɩ"] = "Ɩ", ["ɫ"] = "Ɫ", ["ɯ"] = "Ɯ", ["ɲ"] = "Ɲ", ["ɵ"] = "Ɵ", ["ɽ"] = "Ɽ", ["ʀ"] = "Ʀ", ["ʃ"] = "Ʃ", ["ʈ"] = "Ʈ", ["ʉ"] = "Ʉ", ["ʊ"] = "Ʊ", ["ʋ"] = "Ʋ", ["ʌ"] = "Ʌ", ["ʒ"] = "Ʒ", ["ͅ"] = "Ι", ["ͻ"] = "Ͻ", ["ͼ"] = "Ͼ", ["ͽ"] = "Ͽ", ["ά"] = "Ά", ["έ"] = "Έ", ["ή"] = "Ή", ["ί"] = "Ί", ["α"] = "Α", ["β"] = "Β", ["γ"] = "Γ", ["δ"] = "Δ", ["ε"] = "Ε", ["ζ"] = "Ζ", ["η"] = "Η", ["θ"] = "Θ", ["ι"] = "Ι", ["κ"] = "Κ", ["λ"] = "Λ", ["μ"] = "Μ", ["ν"] = "Ν", ["ξ"] = "Ξ", ["ο"] = "Ο", ["π"] = "Π", ["ρ"] = "Ρ", ["ς"] = "Σ", ["σ"] = "Σ", ["τ"] = "Τ", ["υ"] = "Υ", ["φ"] = "Φ", ["χ"] = "Χ", ["ψ"] = "Ψ", ["ω"] = "Ω", ["ϊ"] = "Ϊ", ["ϋ"] = "Ϋ", ["ό"] = "Ό", ["ύ"] = "Ύ", ["ώ"] = "Ώ", ["ϐ"] = "Β", ["ϑ"] = "Θ", ["ϕ"] = "Φ", ["ϖ"] = "Π", ["ϙ"] = "Ϙ", ["ϛ"] = "Ϛ", ["ϝ"] = "Ϝ", ["ϟ"] = "Ϟ", ["ϡ"] = "Ϡ", ["ϣ"] = "Ϣ", ["ϥ"] = "Ϥ", ["ϧ"] = "Ϧ", ["ϩ"] = "Ϩ", ["ϫ"] = "Ϫ", ["ϭ"] = "Ϭ", ["ϯ"] = "Ϯ", ["ϰ"] = "Κ", ["ϱ"] = "Ρ", ["ϲ"] = "Ϲ", ["ϵ"] = "Ε", ["ϸ"] = "Ϸ", ["ϻ"] = "Ϻ", ["а"] = "А", ["б"] = "Б", ["в"] = "В", ["г"] = "Г", ["д"] = "Д", ["е"] = "Е", ["ж"] = "Ж", ["з"] = "З", ["и"] = "И", ["й"] = "Й", ["к"] = "К", ["л"] = "Л", ["м"] = "М", ["н"] = "Н", ["о"] = "О", ["п"] = "П", ["р"] = "Р", ["с"] = "С", ["т"] = "Т", ["у"] = "У", ["ф"] = "Ф", ["х"] = "Х", ["ц"] = "Ц", ["ч"] = "Ч", ["ш"] = "Ш", ["щ"] = "Щ", ["ъ"] = "Ъ", ["ы"] = "Ы", ["ь"] = "Ь", ["э"] = "Э", ["ю"] = "Ю", ["я"] = "Я", ["ѐ"] = "Ѐ", ["ё"] = "Ё", ["ђ"] = "Ђ", ["ѓ"] = "Ѓ", ["є"] = "Є", ["ѕ"] = "Ѕ", ["і"] = "І", ["ї"] = "Ї", ["ј"] = "Ј", ["љ"] = "Љ", ["њ"] = "Њ", ["ћ"] = "Ћ", ["ќ"] = "Ќ", ["ѝ"] = "Ѝ", ["ў"] = "Ў", ["џ"] = "Џ", ["ѡ"] = "Ѡ", ["ѣ"] = "Ѣ", ["ѥ"] = "Ѥ", ["ѧ"] = "Ѧ", ["ѩ"] = "Ѩ", ["ѫ"] = "Ѫ", ["ѭ"] = "Ѭ", ["ѯ"] = "Ѯ", ["ѱ"] = "Ѱ", ["ѳ"] = "Ѳ", ["ѵ"] = "Ѵ", ["ѷ"] = "Ѷ", ["ѹ"] = "Ѹ", ["ѻ"] = "Ѻ", ["ѽ"] = "Ѽ", ["ѿ"] = "Ѿ", ["ҁ"] = "Ҁ", ["ҋ"] = "Ҋ", ["ҍ"] = "Ҍ", ["ҏ"] = "Ҏ", ["ґ"] = "Ґ", ["ғ"] = "Ғ", ["ҕ"] = "Ҕ", ["җ"] = "Җ", ["ҙ"] = "Ҙ", ["қ"] = "Қ", ["ҝ"] = "Ҝ", ["ҟ"] = "Ҟ", ["ҡ"] = "Ҡ", ["ң"] = "Ң", ["ҥ"] = "Ҥ", ["ҧ"] = "Ҧ", ["ҩ"] = "Ҩ", ["ҫ"] = "Ҫ", ["ҭ"] = "Ҭ", ["ү"] = "Ү", ["ұ"] = "Ұ", ["ҳ"] = "Ҳ", ["ҵ"] = "Ҵ", ["ҷ"] = "Ҷ", ["ҹ"] = "Ҹ", ["һ"] = "Һ", ["ҽ"] = "Ҽ", ["ҿ"] = "Ҿ", ["ӂ"] = "Ӂ", ["ӄ"] = "Ӄ", ["ӆ"] = "Ӆ", ["ӈ"] = "Ӈ", ["ӊ"] = "Ӊ", ["ӌ"] = "Ӌ", ["ӎ"] = "Ӎ", ["ӏ"] = "Ӏ", ["ӑ"] = "Ӑ", ["ӓ"] = "Ӓ", ["ӕ"] = "Ӕ", ["ӗ"] = "Ӗ", ["ә"] = "Ә", ["ӛ"] = "Ӛ", ["ӝ"] = "Ӝ", ["ӟ"] = "Ӟ", ["ӡ"] = "Ӡ", ["ӣ"] = "Ӣ", ["ӥ"] = "Ӥ", ["ӧ"] = "Ӧ", ["ө"] = "Ө", ["ӫ"] = "Ӫ", ["ӭ"] = "Ӭ", ["ӯ"] = "Ӯ", ["ӱ"] = "Ӱ", ["ӳ"] = "Ӳ", ["ӵ"] = "Ӵ", ["ӷ"] = "Ӷ", ["ӹ"] = "Ӹ", ["ӻ"] = "Ӻ", ["ӽ"] = "Ӽ", ["ӿ"] = "Ӿ", ["ԁ"] = "Ԁ", ["ԃ"] = "Ԃ", ["ԅ"] = "Ԅ", ["ԇ"] = "Ԇ", ["ԉ"] = "Ԉ", ["ԋ"] = "Ԋ", ["ԍ"] = "Ԍ", ["ԏ"] = "Ԏ", ["ԑ"] = "Ԑ", ["ԓ"] = "Ԓ", ["ա"] = "Ա", ["բ"] = "Բ", ["գ"] = "Գ", ["դ"] = "Դ", ["ե"] = "Ե", ["զ"] = "Զ", ["է"] = "Է", ["ը"] = "Ը", ["թ"] = "Թ", ["ժ"] = "Ժ", ["ի"] = "Ի", ["լ"] = "Լ", ["խ"] = "Խ", ["ծ"] = "Ծ", ["կ"] = "Կ", ["հ"] = "Հ", ["ձ"] = "Ձ", ["ղ"] = "Ղ", ["ճ"] = "Ճ", ["մ"] = "Մ", ["յ"] = "Յ", ["ն"] = "Ն", ["շ"] = "Շ", ["ո"] = "Ո", ["չ"] = "Չ", ["պ"] = "Պ", ["ջ"] = "Ջ", ["ռ"] = "Ռ", ["ս"] = "Ս", ["վ"] = "Վ", ["տ"] = "Տ", ["ր"] = "Ր", ["ց"] = "Ց", ["ւ"] = "Ւ", ["փ"] = "Փ", ["ք"] = "Ք", ["օ"] = "Օ", ["ֆ"] = "Ֆ", ["ᵽ"] = "Ᵽ", ["ḁ"] = "Ḁ", ["ḃ"] = "Ḃ", ["ḅ"] = "Ḅ", ["ḇ"] = "Ḇ", ["ḉ"] = "Ḉ", ["ḋ"] = "Ḋ", ["ḍ"] = "Ḍ", ["ḏ"] = "Ḏ", ["ḑ"] = "Ḑ", ["ḓ"] = "Ḓ", ["ḕ"] = "Ḕ", ["ḗ"] = "Ḗ", ["ḙ"] = "Ḙ", ["ḛ"] = "Ḛ", ["ḝ"] = "Ḝ", ["ḟ"] = "Ḟ", ["ḡ"] = "Ḡ", ["ḣ"] = "Ḣ", ["ḥ"] = "Ḥ", ["ḧ"] = "Ḧ", ["ḩ"] = "Ḩ", ["ḫ"] = "Ḫ", ["ḭ"] = "Ḭ", ["ḯ"] = "Ḯ", ["ḱ"] = "Ḱ", ["ḳ"] = "Ḳ", ["ḵ"] = "Ḵ", ["ḷ"] = "Ḷ", ["ḹ"] = "Ḹ", ["ḻ"] = "Ḻ", ["ḽ"] = "Ḽ", ["ḿ"] = "Ḿ", ["ṁ"] = "Ṁ", ["ṃ"] = "Ṃ", ["ṅ"] = "Ṅ", ["ṇ"] = "Ṇ", ["ṉ"] = "Ṉ", ["ṋ"] = "Ṋ", ["ṍ"] = "Ṍ", ["ṏ"] = "Ṏ", ["ṑ"] = "Ṑ", ["ṓ"] = "Ṓ", ["ṕ"] = "Ṕ", ["ṗ"] = "Ṗ", ["ṙ"] = "Ṙ", ["ṛ"] = "Ṛ", ["ṝ"] = "Ṝ", ["ṟ"] = "Ṟ", ["ṡ"] = "Ṡ", ["ṣ"] = "Ṣ", ["ṥ"] = "Ṥ", ["ṧ"] = "Ṧ", ["ṩ"] = "Ṩ", ["ṫ"] = "Ṫ", ["ṭ"] = "Ṭ", ["ṯ"] = "Ṯ", ["ṱ"] = "Ṱ", ["ṳ"] = "Ṳ", ["ṵ"] = "Ṵ", ["ṷ"] = "Ṷ", ["ṹ"] = "Ṹ", ["ṻ"] = "Ṻ", ["ṽ"] = "Ṽ", ["ṿ"] = "Ṿ", ["ẁ"] = "Ẁ", ["ẃ"] = "Ẃ", ["ẅ"] = "Ẅ", ["ẇ"] = "Ẇ", ["ẉ"] = "Ẉ", ["ẋ"] = "Ẋ", ["ẍ"] = "Ẍ", ["ẏ"] = "Ẏ", ["ẑ"] = "Ẑ", ["ẓ"] = "Ẓ", ["ẕ"] = "Ẕ", ["ẛ"] = "Ṡ", ["ạ"] = "Ạ", ["ả"] = "Ả", ["ấ"] = "Ấ", ["ầ"] = "Ầ", ["ẩ"] = "Ẩ", ["ẫ"] = "Ẫ", ["ậ"] = "Ậ", ["ắ"] = "Ắ", ["ằ"] = "Ằ", ["ẳ"] = "Ẳ", ["ẵ"] = "Ẵ", ["ặ"] = "Ặ", ["ẹ"] = "Ẹ", ["ẻ"] = "Ẻ", ["ẽ"] = "Ẽ", ["ế"] = "Ế", ["ề"] = "Ề", ["ể"] = "Ể", ["ễ"] = "Ễ", ["ệ"] = "Ệ", ["ỉ"] = "Ỉ", ["ị"] = "Ị", ["ọ"] = "Ọ", ["ỏ"] = "Ỏ", ["ố"] = "Ố", ["ồ"] = "Ồ", ["ổ"] = "Ổ", ["ỗ"] = "Ỗ", ["ộ"] = "Ộ", ["ớ"] = "Ớ", ["ờ"] = "Ờ", ["ở"] = "Ở", ["ỡ"] = "Ỡ", ["ợ"] = "Ợ", ["ụ"] = "Ụ", ["ủ"] = "Ủ", ["ứ"] = "Ứ", ["ừ"] = "Ừ", ["ử"] = "Ử", ["ữ"] = "Ữ", ["ự"] = "Ự", ["ỳ"] = "Ỳ", ["ỵ"] = "Ỵ", ["ỷ"] = "Ỷ", ["ỹ"] = "Ỹ", ["ἀ"] = "Ἀ", ["ἁ"] = "Ἁ", ["ἂ"] = "Ἂ", ["ἃ"] = "Ἃ", ["ἄ"] = "Ἄ", ["ἅ"] = "Ἅ", ["ἆ"] = "Ἆ", ["ἇ"] = "Ἇ", ["ἐ"] = "Ἐ", ["ἑ"] = "Ἑ", ["ἒ"] = "Ἒ", ["ἓ"] = "Ἓ", ["ἔ"] = "Ἔ", ["ἕ"] = "Ἕ", ["ἠ"] = "Ἠ", ["ἡ"] = "Ἡ", ["ἢ"] = "Ἢ", ["ἣ"] = "Ἣ", ["ἤ"] = "Ἤ", ["ἥ"] = "Ἥ", ["ἦ"] = "Ἦ", ["ἧ"] = "Ἧ", ["ἰ"] = "Ἰ", ["ἱ"] = "Ἱ", ["ἲ"] = "Ἲ", ["ἳ"] = "Ἳ", ["ἴ"] = "Ἴ", ["ἵ"] = "Ἵ", ["ἶ"] = "Ἶ", ["ἷ"] = "Ἷ", ["ὀ"] = "Ὀ", ["ὁ"] = "Ὁ", ["ὂ"] = "Ὂ", ["ὃ"] = "Ὃ", ["ὄ"] = "Ὄ", ["ὅ"] = "Ὅ", ["ὑ"] = "Ὑ", ["ὓ"] = "Ὓ", ["ὕ"] = "Ὕ", ["ὗ"] = "Ὗ", ["ὠ"] = "Ὠ", ["ὡ"] = "Ὡ", ["ὢ"] = "Ὢ", ["ὣ"] = "Ὣ", ["ὤ"] = "Ὤ", ["ὥ"] = "Ὥ", ["ὦ"] = "Ὦ", ["ὧ"] = "Ὧ", ["ὰ"] = "Ὰ", ["ά"] = "Ά", ["ὲ"] = "Ὲ", ["έ"] = "Έ", ["ὴ"] = "Ὴ", ["ή"] = "Ή", ["ὶ"] = "Ὶ", ["ί"] = "Ί", ["ὸ"] = "Ὸ", ["ό"] = "Ό", ["ὺ"] = "Ὺ", ["ύ"] = "Ύ", ["ὼ"] = "Ὼ", ["ώ"] = "Ώ", ["ᾀ"] = "ᾈ", ["ᾁ"] = "ᾉ", ["ᾂ"] = "ᾊ", ["ᾃ"] = "ᾋ", ["ᾄ"] = "ᾌ", ["ᾅ"] = "ᾍ", ["ᾆ"] = "ᾎ", ["ᾇ"] = "ᾏ", ["ᾐ"] = "ᾘ", ["ᾑ"] = "ᾙ", ["ᾒ"] = "ᾚ", ["ᾓ"] = "ᾛ", ["ᾔ"] = "ᾜ", ["ᾕ"] = "ᾝ", ["ᾖ"] = "ᾞ", ["ᾗ"] = "ᾟ", ["ᾠ"] = "ᾨ", ["ᾡ"] = "ᾩ", ["ᾢ"] = "ᾪ", ["ᾣ"] = "ᾫ", ["ᾤ"] = "ᾬ", ["ᾥ"] = "ᾭ", ["ᾦ"] = "ᾮ", ["ᾧ"] = "ᾯ", ["ᾰ"] = "Ᾰ", ["ᾱ"] = "Ᾱ", ["ᾳ"] = "ᾼ", ["ι"] = "Ι", ["ῃ"] = "ῌ", ["ῐ"] = "Ῐ", ["ῑ"] = "Ῑ", ["ῠ"] = "Ῠ", ["ῡ"] = "Ῡ", ["ῥ"] = "Ῥ", ["ῳ"] = "ῼ", ["ⅎ"] = "Ⅎ", ["ⅰ"] = "Ⅰ", ["ⅱ"] = "Ⅱ", ["ⅲ"] = "Ⅲ", ["ⅳ"] = "Ⅳ", ["ⅴ"] = "Ⅴ", ["ⅵ"] = "Ⅵ", ["ⅶ"] = "Ⅶ", ["ⅷ"] = "Ⅷ", ["ⅸ"] = "Ⅸ", ["ⅹ"] = "Ⅹ", ["ⅺ"] = "Ⅺ", ["ⅻ"] = "Ⅻ", ["ⅼ"] = "Ⅼ", ["ⅽ"] = "Ⅽ", ["ⅾ"] = "Ⅾ", ["ⅿ"] = "Ⅿ", ["ↄ"] = "Ↄ", ["ⓐ"] = "Ⓐ", ["ⓑ"] = "Ⓑ", ["ⓒ"] = "Ⓒ", ["ⓓ"] = "Ⓓ", ["ⓔ"] = "Ⓔ", ["ⓕ"] = "Ⓕ", ["ⓖ"] = "Ⓖ", ["ⓗ"] = "Ⓗ", ["ⓘ"] = "Ⓘ", ["ⓙ"] = "Ⓙ", ["ⓚ"] = "Ⓚ", ["ⓛ"] = "Ⓛ", ["ⓜ"] = "Ⓜ", ["ⓝ"] = "Ⓝ", ["ⓞ"] = "Ⓞ", ["ⓟ"] = "Ⓟ", ["ⓠ"] = "Ⓠ", ["ⓡ"] = "Ⓡ", ["ⓢ"] = "Ⓢ", ["ⓣ"] = "Ⓣ", ["ⓤ"] = "Ⓤ", ["ⓥ"] = "Ⓥ", ["ⓦ"] = "Ⓦ", ["ⓧ"] = "Ⓧ", ["ⓨ"] = "Ⓨ", ["ⓩ"] = "Ⓩ", ["ⰰ"] = "Ⰰ", ["ⰱ"] = "Ⰱ", ["ⰲ"] = "Ⰲ", ["ⰳ"] = "Ⰳ", ["ⰴ"] = "Ⰴ", ["ⰵ"] = "Ⰵ", ["ⰶ"] = "Ⰶ", ["ⰷ"] = "Ⰷ", ["ⰸ"] = "Ⰸ", ["ⰹ"] = "Ⰹ", ["ⰺ"] = "Ⰺ", ["ⰻ"] = "Ⰻ", ["ⰼ"] = "Ⰼ", ["ⰽ"] = "Ⰽ", ["ⰾ"] = "Ⰾ", ["ⰿ"] = "Ⰿ", ["ⱀ"] = "Ⱀ", ["ⱁ"] = "Ⱁ", ["ⱂ"] = "Ⱂ", ["ⱃ"] = "Ⱃ", ["ⱄ"] = "Ⱄ", ["ⱅ"] = "Ⱅ", ["ⱆ"] = "Ⱆ", ["ⱇ"] = "Ⱇ", ["ⱈ"] = "Ⱈ", ["ⱉ"] = "Ⱉ", ["ⱊ"] = "Ⱊ", ["ⱋ"] = "Ⱋ", ["ⱌ"] = "Ⱌ", ["ⱍ"] = "Ⱍ", ["ⱎ"] = "Ⱎ", ["ⱏ"] = "Ⱏ", ["ⱐ"] = "Ⱐ", ["ⱑ"] = "Ⱑ", ["ⱒ"] = "Ⱒ", ["ⱓ"] = "Ⱓ", ["ⱔ"] = "Ⱔ", ["ⱕ"] = "Ⱕ", ["ⱖ"] = "Ⱖ", ["ⱗ"] = "Ⱗ", ["ⱘ"] = "Ⱘ", ["ⱙ"] = "Ⱙ", ["ⱚ"] = "Ⱚ", ["ⱛ"] = "Ⱛ", ["ⱜ"] = "Ⱜ", ["ⱝ"] = "Ⱝ", ["ⱞ"] = "Ⱞ", ["ⱡ"] = "Ⱡ", ["ⱥ"] = "Ⱥ", ["ⱦ"] = "Ⱦ", ["ⱨ"] = "Ⱨ", ["ⱪ"] = "Ⱪ", ["ⱬ"] = "Ⱬ", ["ⱶ"] = "Ⱶ", ["ⲁ"] = "Ⲁ", ["ⲃ"] = "Ⲃ", ["ⲅ"] = "Ⲅ", ["ⲇ"] = "Ⲇ", ["ⲉ"] = "Ⲉ", ["ⲋ"] = "Ⲋ", ["ⲍ"] = "Ⲍ", ["ⲏ"] = "Ⲏ", ["ⲑ"] = "Ⲑ", ["ⲓ"] = "Ⲓ", ["ⲕ"] = "Ⲕ", ["ⲗ"] = "Ⲗ", ["ⲙ"] = "Ⲙ", ["ⲛ"] = "Ⲛ", ["ⲝ"] = "Ⲝ", ["ⲟ"] = "Ⲟ", ["ⲡ"] = "Ⲡ", ["ⲣ"] = "Ⲣ", ["ⲥ"] = "Ⲥ", ["ⲧ"] = "Ⲧ", ["ⲩ"] = "Ⲩ", ["ⲫ"] = "Ⲫ", ["ⲭ"] = "Ⲭ", ["ⲯ"] = "Ⲯ", ["ⲱ"] = "Ⲱ", ["ⲳ"] = "Ⲳ", ["ⲵ"] = "Ⲵ", ["ⲷ"] = "Ⲷ", ["ⲹ"] = "Ⲹ", ["ⲻ"] = "Ⲻ", ["ⲽ"] = "Ⲽ", ["ⲿ"] = "Ⲿ", ["ⳁ"] = "Ⳁ", ["ⳃ"] = "Ⳃ", ["ⳅ"] = "Ⳅ", ["ⳇ"] = "Ⳇ", ["ⳉ"] = "Ⳉ", ["ⳋ"] = "Ⳋ", ["ⳍ"] = "Ⳍ", ["ⳏ"] = "Ⳏ", ["ⳑ"] = "Ⳑ", ["ⳓ"] = "Ⳓ", ["ⳕ"] = "Ⳕ", ["ⳗ"] = "Ⳗ", ["ⳙ"] = "Ⳙ", ["ⳛ"] = "Ⳛ", ["ⳝ"] = "Ⳝ", ["ⳟ"] = "Ⳟ", ["ⳡ"] = "Ⳡ", ["ⳣ"] = "Ⳣ", ["ⴀ"] = "Ⴀ", ["ⴁ"] = "Ⴁ", ["ⴂ"] = "Ⴂ", ["ⴃ"] = "Ⴃ", ["ⴄ"] = "Ⴄ", ["ⴅ"] = "Ⴅ", ["ⴆ"] = "Ⴆ", ["ⴇ"] = "Ⴇ", ["ⴈ"] = "Ⴈ", ["ⴉ"] = "Ⴉ", ["ⴊ"] = "Ⴊ", ["ⴋ"] = "Ⴋ", ["ⴌ"] = "Ⴌ", ["ⴍ"] = "Ⴍ", ["ⴎ"] = "Ⴎ", ["ⴏ"] = "Ⴏ", ["ⴐ"] = "Ⴐ", ["ⴑ"] = "Ⴑ", ["ⴒ"] = "Ⴒ", ["ⴓ"] = "Ⴓ", ["ⴔ"] = "Ⴔ", ["ⴕ"] = "Ⴕ", ["ⴖ"] = "Ⴖ", ["ⴗ"] = "Ⴗ", ["ⴘ"] = "Ⴘ", ["ⴙ"] = "Ⴙ", ["ⴚ"] = "Ⴚ", ["ⴛ"] = "Ⴛ", ["ⴜ"] = "Ⴜ", ["ⴝ"] = "Ⴝ", ["ⴞ"] = "Ⴞ", ["ⴟ"] = "Ⴟ", ["ⴠ"] = "Ⴠ", ["ⴡ"] = "Ⴡ", ["ⴢ"] = "Ⴢ", ["ⴣ"] = "Ⴣ", ["ⴤ"] = "Ⴤ", ["ⴥ"] = "Ⴥ", ["a"] = "A", ["b"] = "B", ["c"] = "C", ["d"] = "D", ["e"] = "E", ["f"] = "F", ["g"] = "G", ["h"] = "H", ["i"] = "I", ["j"] = "J", ["k"] = "K", ["l"] = "L", ["m"] = "M", ["n"] = "N", ["o"] = "O", ["p"] = "P", ["q"] = "Q", ["r"] = "R", ["s"] = "S", ["t"] = "T", ["u"] = "U", ["v"] = "V", ["w"] = "W", ["x"] = "X", ["y"] = "Y", ["z"] = "Z", ["𐐨"] = "𐐀", ["𐐩"] = "𐐁", ["𐐪"] = "𐐂", ["𐐫"] = "𐐃", ["𐐬"] = "𐐄", ["𐐭"] = "𐐅", ["𐐮"] = "𐐆", ["𐐯"] = "𐐇", ["𐐰"] = "𐐈", ["𐐱"] = "𐐉", ["𐐲"] = "𐐊", ["𐐳"] = "𐐋", ["𐐴"] = "𐐌", ["𐐵"] = "𐐍", ["𐐶"] = "𐐎", ["𐐷"] = "𐐏", ["𐐸"] = "𐐐", ["𐐹"] = "𐐑", ["𐐺"] = "𐐒", ["𐐻"] = "𐐓", ["𐐼"] = "𐐔", ["𐐽"] = "𐐕", ["𐐾"] = "𐐖", ["𐐿"] = "𐐗", ["𐑀"] = "𐐘", ["𐑁"] = "𐐙", ["𐑂"] = "𐐚", ["𐑃"] = "𐐛", ["𐑄"] = "𐐜", ["𐑅"] = "𐐝", ["𐑆"] = "𐐞", ["𐑇"] = "𐐟", ["𐑈"] = "𐐠", ["𐑉"] = "𐐡", ["𐑊"] = "𐐢", ["𐑋"] = "𐐣", ["𐑌"] = "𐐤", ["𐑍"] = "𐐥", ["𐑎"] = "𐐦", ["𐑏"] = "𐐧", } utf8_uc_lc = { ["A"] = "a", ["B"] = "b", ["C"] = "c", ["D"] = "d", ["E"] = "e", ["F"] = "f", ["G"] = "g", ["H"] = "h", ["I"] = "i", ["J"] = "j", ["K"] = "k", ["L"] = "l", ["M"] = "m", ["N"] = "n", ["O"] = "o", ["P"] = "p", ["Q"] = "q", ["R"] = "r", ["S"] = "s", ["T"] = "t", ["U"] = "u", ["V"] = "v", ["W"] = "w", ["X"] = "x", ["Y"] = "y", ["Z"] = "z", ["À"] = "à", ["Á"] = "á", ["Â"] = "â", ["Ã"] = "ã", ["Ä"] = "ä", ["Å"] = "å", ["Æ"] = "æ", ["Ç"] = "ç", ["È"] = "è", ["É"] = "é", ["Ê"] = "ê", ["Ë"] = "ë", ["Ì"] = "ì", ["Í"] = "í", ["Î"] = "î", ["Ï"] = "ï", ["Ð"] = "ð", ["Ñ"] = "ñ", ["Ò"] = "ò", ["Ó"] = "ó", ["Ô"] = "ô", ["Õ"] = "õ", ["Ö"] = "ö", ["Ø"] = "ø", ["Ù"] = "ù", ["Ú"] = "ú", ["Û"] = "û", ["Ü"] = "ü", ["Ý"] = "ý", ["Þ"] = "þ", ["Ā"] = "ā", ["Ă"] = "ă", ["Ą"] = "ą", ["Ć"] = "ć", ["Ĉ"] = "ĉ", ["Ċ"] = "ċ", ["Č"] = "č", ["Ď"] = "ď", ["Đ"] = "đ", ["Ē"] = "ē", ["Ĕ"] = "ĕ", ["Ė"] = "ė", ["Ę"] = "ę", ["Ě"] = "ě", ["Ĝ"] = "ĝ", ["Ğ"] = "ğ", ["Ġ"] = "ġ", ["Ģ"] = "ģ", ["Ĥ"] = "ĥ", ["Ħ"] = "ħ", ["Ĩ"] = "ĩ", ["Ī"] = "ī", ["Ĭ"] = "ĭ", ["Į"] = "į", ["İ"] = "i", ["IJ"] = "ij", ["Ĵ"] = "ĵ", ["Ķ"] = "ķ", ["Ĺ"] = "ĺ", ["Ļ"] = "ļ", ["Ľ"] = "ľ", ["Ŀ"] = "ŀ", ["Ł"] = "ł", ["Ń"] = "ń", ["Ņ"] = "ņ", ["Ň"] = "ň", ["Ŋ"] = "ŋ", ["Ō"] = "ō", ["Ŏ"] = "ŏ", ["Ő"] = "ő", ["Œ"] = "œ", ["Ŕ"] = "ŕ", ["Ŗ"] = "ŗ", ["Ř"] = "ř", ["Ś"] = "ś", ["Ŝ"] = "ŝ", ["Ş"] = "ş", ["Š"] = "š", ["Ţ"] = "ţ", ["Ť"] = "ť", ["Ŧ"] = "ŧ", ["Ũ"] = "ũ", ["Ū"] = "ū", ["Ŭ"] = "ŭ", ["Ů"] = "ů", ["Ű"] = "ű", ["Ų"] = "ų", ["Ŵ"] = "ŵ", ["Ŷ"] = "ŷ", ["Ÿ"] = "ÿ", ["Ź"] = "ź", ["Ż"] = "ż", ["Ž"] = "ž", ["Ɓ"] = "ɓ", ["Ƃ"] = "ƃ", ["Ƅ"] = "ƅ", ["Ɔ"] = "ɔ", ["Ƈ"] = "ƈ", ["Ɖ"] = "ɖ", ["Ɗ"] = "ɗ", ["Ƌ"] = "ƌ", ["Ǝ"] = "ǝ", ["Ə"] = "ə", ["Ɛ"] = "ɛ", ["Ƒ"] = "ƒ", ["Ɠ"] = "ɠ", ["Ɣ"] = "ɣ", ["Ɩ"] = "ɩ", ["Ɨ"] = "ɨ", ["Ƙ"] = "ƙ", ["Ɯ"] = "ɯ", ["Ɲ"] = "ɲ", ["Ɵ"] = "ɵ", ["Ơ"] = "ơ", ["Ƣ"] = "ƣ", ["Ƥ"] = "ƥ", ["Ʀ"] = "ʀ", ["Ƨ"] = "ƨ", ["Ʃ"] = "ʃ", ["Ƭ"] = "ƭ", ["Ʈ"] = "ʈ", ["Ư"] = "ư", ["Ʊ"] = "ʊ", ["Ʋ"] = "ʋ", ["Ƴ"] = "ƴ", ["Ƶ"] = "ƶ", ["Ʒ"] = "ʒ", ["Ƹ"] = "ƹ", ["Ƽ"] = "ƽ", ["DŽ"] = "dž", ["Dž"] = "dž", ["LJ"] = "lj", ["Lj"] = "lj", ["NJ"] = "nj", ["Nj"] = "nj", ["Ǎ"] = "ǎ", ["Ǐ"] = "ǐ", ["Ǒ"] = "ǒ", ["Ǔ"] = "ǔ", ["Ǖ"] = "ǖ", ["Ǘ"] = "ǘ", ["Ǚ"] = "ǚ", ["Ǜ"] = "ǜ", ["Ǟ"] = "ǟ", ["Ǡ"] = "ǡ", ["Ǣ"] = "ǣ", ["Ǥ"] = "ǥ", ["Ǧ"] = "ǧ", ["Ǩ"] = "ǩ", ["Ǫ"] = "ǫ", ["Ǭ"] = "ǭ", ["Ǯ"] = "ǯ", ["DZ"] = "dz", ["Dz"] = "dz", ["Ǵ"] = "ǵ", ["Ƕ"] = "ƕ", ["Ƿ"] = "ƿ", ["Ǹ"] = "ǹ", ["Ǻ"] = "ǻ", ["Ǽ"] = "ǽ", ["Ǿ"] = "ǿ", ["Ȁ"] = "ȁ", ["Ȃ"] = "ȃ", ["Ȅ"] = "ȅ", ["Ȇ"] = "ȇ", ["Ȉ"] = "ȉ", ["Ȋ"] = "ȋ", ["Ȍ"] = "ȍ", ["Ȏ"] = "ȏ", ["Ȑ"] = "ȑ", ["Ȓ"] = "ȓ", ["Ȕ"] = "ȕ", ["Ȗ"] = "ȗ", ["Ș"] = "ș", ["Ț"] = "ț", ["Ȝ"] = "ȝ", ["Ȟ"] = "ȟ", ["Ƞ"] = "ƞ", ["Ȣ"] = "ȣ", ["Ȥ"] = "ȥ", ["Ȧ"] = "ȧ", ["Ȩ"] = "ȩ", ["Ȫ"] = "ȫ", ["Ȭ"] = "ȭ", ["Ȯ"] = "ȯ", ["Ȱ"] = "ȱ", ["Ȳ"] = "ȳ", ["Ⱥ"] = "ⱥ", ["Ȼ"] = "ȼ", ["Ƚ"] = "ƚ", ["Ⱦ"] = "ⱦ", ["Ɂ"] = "ɂ", ["Ƀ"] = "ƀ", ["Ʉ"] = "ʉ", ["Ʌ"] = "ʌ", ["Ɇ"] = "ɇ", ["Ɉ"] = "ɉ", ["Ɋ"] = "ɋ", ["Ɍ"] = "ɍ", ["Ɏ"] = "ɏ", ["Ά"] = "ά", ["Έ"] = "έ", ["Ή"] = "ή", ["Ί"] = "ί", ["Ό"] = "ό", ["Ύ"] = "ύ", ["Ώ"] = "ώ", ["Α"] = "α", ["Β"] = "β", ["Γ"] = "γ", ["Δ"] = "δ", ["Ε"] = "ε", ["Ζ"] = "ζ", ["Η"] = "η", ["Θ"] = "θ", ["Ι"] = "ι", ["Κ"] = "κ", ["Λ"] = "λ", ["Μ"] = "μ", ["Ν"] = "ν", ["Ξ"] = "ξ", ["Ο"] = "ο", ["Π"] = "π", ["Ρ"] = "ρ", ["Σ"] = "σ", ["Τ"] = "τ", ["Υ"] = "υ", ["Φ"] = "φ", ["Χ"] = "χ", ["Ψ"] = "ψ", ["Ω"] = "ω", ["Ϊ"] = "ϊ", ["Ϋ"] = "ϋ", ["Ϙ"] = "ϙ", ["Ϛ"] = "ϛ", ["Ϝ"] = "ϝ", ["Ϟ"] = "ϟ", ["Ϡ"] = "ϡ", ["Ϣ"] = "ϣ", ["Ϥ"] = "ϥ", ["Ϧ"] = "ϧ", ["Ϩ"] = "ϩ", ["Ϫ"] = "ϫ", ["Ϭ"] = "ϭ", ["Ϯ"] = "ϯ", ["ϴ"] = "θ", ["Ϸ"] = "ϸ", ["Ϲ"] = "ϲ", ["Ϻ"] = "ϻ", ["Ͻ"] = "ͻ", ["Ͼ"] = "ͼ", ["Ͽ"] = "ͽ", ["Ѐ"] = "ѐ", ["Ё"] = "ё", ["Ђ"] = "ђ", ["Ѓ"] = "ѓ", ["Є"] = "є", ["Ѕ"] = "ѕ", ["І"] = "і", ["Ї"] = "ї", ["Ј"] = "ј", ["Љ"] = "љ", ["Њ"] = "њ", ["Ћ"] = "ћ", ["Ќ"] = "ќ", ["Ѝ"] = "ѝ", ["Ў"] = "ў", ["Џ"] = "џ", ["А"] = "а", ["Б"] = "б", ["В"] = "в", ["Г"] = "г", ["Д"] = "д", ["Е"] = "е", ["Ж"] = "ж", ["З"] = "з", ["И"] = "и", ["Й"] = "й", ["К"] = "к", ["Л"] = "л", ["М"] = "м", ["Н"] = "н", ["О"] = "о", ["П"] = "п", ["Р"] = "р", ["С"] = "с", ["Т"] = "т", ["У"] = "у", ["Ф"] = "ф", ["Х"] = "х", ["Ц"] = "ц", ["Ч"] = "ч", ["Ш"] = "ш", ["Щ"] = "щ", ["Ъ"] = "ъ", ["Ы"] = "ы", ["Ь"] = "ь", ["Э"] = "э", ["Ю"] = "ю", ["Я"] = "я", ["Ѡ"] = "ѡ", ["Ѣ"] = "ѣ", ["Ѥ"] = "ѥ", ["Ѧ"] = "ѧ", ["Ѩ"] = "ѩ", ["Ѫ"] = "ѫ", ["Ѭ"] = "ѭ", ["Ѯ"] = "ѯ", ["Ѱ"] = "ѱ", ["Ѳ"] = "ѳ", ["Ѵ"] = "ѵ", ["Ѷ"] = "ѷ", ["Ѹ"] = "ѹ", ["Ѻ"] = "ѻ", ["Ѽ"] = "ѽ", ["Ѿ"] = "ѿ", ["Ҁ"] = "ҁ", ["Ҋ"] = "ҋ", ["Ҍ"] = "ҍ", ["Ҏ"] = "ҏ", ["Ґ"] = "ґ", ["Ғ"] = "ғ", ["Ҕ"] = "ҕ", ["Җ"] = "җ", ["Ҙ"] = "ҙ", ["Қ"] = "қ", ["Ҝ"] = "ҝ", ["Ҟ"] = "ҟ", ["Ҡ"] = "ҡ", ["Ң"] = "ң", ["Ҥ"] = "ҥ", ["Ҧ"] = "ҧ", ["Ҩ"] = "ҩ", ["Ҫ"] = "ҫ", ["Ҭ"] = "ҭ", ["Ү"] = "ү", ["Ұ"] = "ұ", ["Ҳ"] = "ҳ", ["Ҵ"] = "ҵ", ["Ҷ"] = "ҷ", ["Ҹ"] = "ҹ", ["Һ"] = "һ", ["Ҽ"] = "ҽ", ["Ҿ"] = "ҿ", ["Ӏ"] = "ӏ", ["Ӂ"] = "ӂ", ["Ӄ"] = "ӄ", ["Ӆ"] = "ӆ", ["Ӈ"] = "ӈ", ["Ӊ"] = "ӊ", ["Ӌ"] = "ӌ", ["Ӎ"] = "ӎ", ["Ӑ"] = "ӑ", ["Ӓ"] = "ӓ", ["Ӕ"] = "ӕ", ["Ӗ"] = "ӗ", ["Ә"] = "ә", ["Ӛ"] = "ӛ", ["Ӝ"] = "ӝ", ["Ӟ"] = "ӟ", ["Ӡ"] = "ӡ", ["Ӣ"] = "ӣ", ["Ӥ"] = "ӥ", ["Ӧ"] = "ӧ", ["Ө"] = "ө", ["Ӫ"] = "ӫ", ["Ӭ"] = "ӭ", ["Ӯ"] = "ӯ", ["Ӱ"] = "ӱ", ["Ӳ"] = "ӳ", ["Ӵ"] = "ӵ", ["Ӷ"] = "ӷ", ["Ӹ"] = "ӹ", ["Ӻ"] = "ӻ", ["Ӽ"] = "ӽ", ["Ӿ"] = "ӿ", ["Ԁ"] = "ԁ", ["Ԃ"] = "ԃ", ["Ԅ"] = "ԅ", ["Ԇ"] = "ԇ", ["Ԉ"] = "ԉ", ["Ԋ"] = "ԋ", ["Ԍ"] = "ԍ", ["Ԏ"] = "ԏ", ["Ԑ"] = "ԑ", ["Ԓ"] = "ԓ", ["Ա"] = "ա", ["Բ"] = "բ", ["Գ"] = "գ", ["Դ"] = "դ", ["Ե"] = "ե", ["Զ"] = "զ", ["Է"] = "է", ["Ը"] = "ը", ["Թ"] = "թ", ["Ժ"] = "ժ", ["Ի"] = "ի", ["Լ"] = "լ", ["Խ"] = "խ", ["Ծ"] = "ծ", ["Կ"] = "կ", ["Հ"] = "հ", ["Ձ"] = "ձ", ["Ղ"] = "ղ", ["Ճ"] = "ճ", ["Մ"] = "մ", ["Յ"] = "յ", ["Ն"] = "ն", ["Շ"] = "շ", ["Ո"] = "ո", ["Չ"] = "չ", ["Պ"] = "պ", ["Ջ"] = "ջ", ["Ռ"] = "ռ", ["Ս"] = "ս", ["Վ"] = "վ", ["Տ"] = "տ", ["Ր"] = "ր", ["Ց"] = "ց", ["Ւ"] = "ւ", ["Փ"] = "փ", ["Ք"] = "ք", ["Օ"] = "օ", ["Ֆ"] = "ֆ", ["Ⴀ"] = "ⴀ", ["Ⴁ"] = "ⴁ", ["Ⴂ"] = "ⴂ", ["Ⴃ"] = "ⴃ", ["Ⴄ"] = "ⴄ", ["Ⴅ"] = "ⴅ", ["Ⴆ"] = "ⴆ", ["Ⴇ"] = "ⴇ", ["Ⴈ"] = "ⴈ", ["Ⴉ"] = "ⴉ", ["Ⴊ"] = "ⴊ", ["Ⴋ"] = "ⴋ", ["Ⴌ"] = "ⴌ", ["Ⴍ"] = "ⴍ", ["Ⴎ"] = "ⴎ", ["Ⴏ"] = "ⴏ", ["Ⴐ"] = "ⴐ", ["Ⴑ"] = "ⴑ", ["Ⴒ"] = "ⴒ", ["Ⴓ"] = "ⴓ", ["Ⴔ"] = "ⴔ", ["Ⴕ"] = "ⴕ", ["Ⴖ"] = "ⴖ", ["Ⴗ"] = "ⴗ", ["Ⴘ"] = "ⴘ", ["Ⴙ"] = "ⴙ", ["Ⴚ"] = "ⴚ", ["Ⴛ"] = "ⴛ", ["Ⴜ"] = "ⴜ", ["Ⴝ"] = "ⴝ", ["Ⴞ"] = "ⴞ", ["Ⴟ"] = "ⴟ", ["Ⴠ"] = "ⴠ", ["Ⴡ"] = "ⴡ", ["Ⴢ"] = "ⴢ", ["Ⴣ"] = "ⴣ", ["Ⴤ"] = "ⴤ", ["Ⴥ"] = "ⴥ", ["Ḁ"] = "ḁ", ["Ḃ"] = "ḃ", ["Ḅ"] = "ḅ", ["Ḇ"] = "ḇ", ["Ḉ"] = "ḉ", ["Ḋ"] = "ḋ", ["Ḍ"] = "ḍ", ["Ḏ"] = "ḏ", ["Ḑ"] = "ḑ", ["Ḓ"] = "ḓ", ["Ḕ"] = "ḕ", ["Ḗ"] = "ḗ", ["Ḙ"] = "ḙ", ["Ḛ"] = "ḛ", ["Ḝ"] = "ḝ", ["Ḟ"] = "ḟ", ["Ḡ"] = "ḡ", ["Ḣ"] = "ḣ", ["Ḥ"] = "ḥ", ["Ḧ"] = "ḧ", ["Ḩ"] = "ḩ", ["Ḫ"] = "ḫ", ["Ḭ"] = "ḭ", ["Ḯ"] = "ḯ", ["Ḱ"] = "ḱ", ["Ḳ"] = "ḳ", ["Ḵ"] = "ḵ", ["Ḷ"] = "ḷ", ["Ḹ"] = "ḹ", ["Ḻ"] = "ḻ", ["Ḽ"] = "ḽ", ["Ḿ"] = "ḿ", ["Ṁ"] = "ṁ", ["Ṃ"] = "ṃ", ["Ṅ"] = "ṅ", ["Ṇ"] = "ṇ", ["Ṉ"] = "ṉ", ["Ṋ"] = "ṋ", ["Ṍ"] = "ṍ", ["Ṏ"] = "ṏ", ["Ṑ"] = "ṑ", ["Ṓ"] = "ṓ", ["Ṕ"] = "ṕ", ["Ṗ"] = "ṗ", ["Ṙ"] = "ṙ", ["Ṛ"] = "ṛ", ["Ṝ"] = "ṝ", ["Ṟ"] = "ṟ", ["Ṡ"] = "ṡ", ["Ṣ"] = "ṣ", ["Ṥ"] = "ṥ", ["Ṧ"] = "ṧ", ["Ṩ"] = "ṩ", ["Ṫ"] = "ṫ", ["Ṭ"] = "ṭ", ["Ṯ"] = "ṯ", ["Ṱ"] = "ṱ", ["Ṳ"] = "ṳ", ["Ṵ"] = "ṵ", ["Ṷ"] = "ṷ", ["Ṹ"] = "ṹ", ["Ṻ"] = "ṻ", ["Ṽ"] = "ṽ", ["Ṿ"] = "ṿ", ["Ẁ"] = "ẁ", ["Ẃ"] = "ẃ", ["Ẅ"] = "ẅ", ["Ẇ"] = "ẇ", ["Ẉ"] = "ẉ", ["Ẋ"] = "ẋ", ["Ẍ"] = "ẍ", ["Ẏ"] = "ẏ", ["Ẑ"] = "ẑ", ["Ẓ"] = "ẓ", ["Ẕ"] = "ẕ", ["Ạ"] = "ạ", ["Ả"] = "ả", ["Ấ"] = "ấ", ["Ầ"] = "ầ", ["Ẩ"] = "ẩ", ["Ẫ"] = "ẫ", ["Ậ"] = "ậ", ["Ắ"] = "ắ", ["Ằ"] = "ằ", ["Ẳ"] = "ẳ", ["Ẵ"] = "ẵ", ["Ặ"] = "ặ", ["Ẹ"] = "ẹ", ["Ẻ"] = "ẻ", ["Ẽ"] = "ẽ", ["Ế"] = "ế", ["Ề"] = "ề", ["Ể"] = "ể", ["Ễ"] = "ễ", ["Ệ"] = "ệ", ["Ỉ"] = "ỉ", ["Ị"] = "ị", ["Ọ"] = "ọ", ["Ỏ"] = "ỏ", ["Ố"] = "ố", ["Ồ"] = "ồ", ["Ổ"] = "ổ", ["Ỗ"] = "ỗ", ["Ộ"] = "ộ", ["Ớ"] = "ớ", ["Ờ"] = "ờ", ["Ở"] = "ở", ["Ỡ"] = "ỡ", ["Ợ"] = "ợ", ["Ụ"] = "ụ", ["Ủ"] = "ủ", ["Ứ"] = "ứ", ["Ừ"] = "ừ", ["Ử"] = "ử", ["Ữ"] = "ữ", ["Ự"] = "ự", ["Ỳ"] = "ỳ", ["Ỵ"] = "ỵ", ["Ỷ"] = "ỷ", ["Ỹ"] = "ỹ", ["Ἀ"] = "ἀ", ["Ἁ"] = "ἁ", ["Ἂ"] = "ἂ", ["Ἃ"] = "ἃ", ["Ἄ"] = "ἄ", ["Ἅ"] = "ἅ", ["Ἆ"] = "ἆ", ["Ἇ"] = "ἇ", ["Ἐ"] = "ἐ", ["Ἑ"] = "ἑ", ["Ἒ"] = "ἒ", ["Ἓ"] = "ἓ", ["Ἔ"] = "ἔ", ["Ἕ"] = "ἕ", ["Ἠ"] = "ἠ", ["Ἡ"] = "ἡ", ["Ἢ"] = "ἢ", ["Ἣ"] = "ἣ", ["Ἤ"] = "ἤ", ["Ἥ"] = "ἥ", ["Ἦ"] = "ἦ", ["Ἧ"] = "ἧ", ["Ἰ"] = "ἰ", ["Ἱ"] = "ἱ", ["Ἲ"] = "ἲ", ["Ἳ"] = "ἳ", ["Ἴ"] = "ἴ", ["Ἵ"] = "ἵ", ["Ἶ"] = "ἶ", ["Ἷ"] = "ἷ", ["Ὀ"] = "ὀ", ["Ὁ"] = "ὁ", ["Ὂ"] = "ὂ", ["Ὃ"] = "ὃ", ["Ὄ"] = "ὄ", ["Ὅ"] = "ὅ", ["Ὑ"] = "ὑ", ["Ὓ"] = "ὓ", ["Ὕ"] = "ὕ", ["Ὗ"] = "ὗ", ["Ὠ"] = "ὠ", ["Ὡ"] = "ὡ", ["Ὢ"] = "ὢ", ["Ὣ"] = "ὣ", ["Ὤ"] = "ὤ", ["Ὥ"] = "ὥ", ["Ὦ"] = "ὦ", ["Ὧ"] = "ὧ", ["ᾈ"] = "ᾀ", ["ᾉ"] = "ᾁ", ["ᾊ"] = "ᾂ", ["ᾋ"] = "ᾃ", ["ᾌ"] = "ᾄ", ["ᾍ"] = "ᾅ", ["ᾎ"] = "ᾆ", ["ᾏ"] = "ᾇ", ["ᾘ"] = "ᾐ", ["ᾙ"] = "ᾑ", ["ᾚ"] = "ᾒ", ["ᾛ"] = "ᾓ", ["ᾜ"] = "ᾔ", ["ᾝ"] = "ᾕ", ["ᾞ"] = "ᾖ", ["ᾟ"] = "ᾗ", ["ᾨ"] = "ᾠ", ["ᾩ"] = "ᾡ", ["ᾪ"] = "ᾢ", ["ᾫ"] = "ᾣ", ["ᾬ"] = "ᾤ", ["ᾭ"] = "ᾥ", ["ᾮ"] = "ᾦ", ["ᾯ"] = "ᾧ", ["Ᾰ"] = "ᾰ", ["Ᾱ"] = "ᾱ", ["Ὰ"] = "ὰ", ["Ά"] = "ά", ["ᾼ"] = "ᾳ", ["Ὲ"] = "ὲ", ["Έ"] = "έ", ["Ὴ"] = "ὴ", ["Ή"] = "ή", ["ῌ"] = "ῃ", ["Ῐ"] = "ῐ", ["Ῑ"] = "ῑ", ["Ὶ"] = "ὶ", ["Ί"] = "ί", ["Ῠ"] = "ῠ", ["Ῡ"] = "ῡ", ["Ὺ"] = "ὺ", ["Ύ"] = "ύ", ["Ῥ"] = "ῥ", ["Ὸ"] = "ὸ", ["Ό"] = "ό", ["Ὼ"] = "ὼ", ["Ώ"] = "ώ", ["ῼ"] = "ῳ", ["Ω"] = "ω", ["K"] = "k", ["Å"] = "å", ["Ⅎ"] = "ⅎ", ["Ⅰ"] = "ⅰ", ["Ⅱ"] = "ⅱ", ["Ⅲ"] = "ⅲ", ["Ⅳ"] = "ⅳ", ["Ⅴ"] = "ⅴ", ["Ⅵ"] = "ⅵ", ["Ⅶ"] = "ⅶ", ["Ⅷ"] = "ⅷ", ["Ⅸ"] = "ⅸ", ["Ⅹ"] = "ⅹ", ["Ⅺ"] = "ⅺ", ["Ⅻ"] = "ⅻ", ["Ⅼ"] = "ⅼ", ["Ⅽ"] = "ⅽ", ["Ⅾ"] = "ⅾ", ["Ⅿ"] = "ⅿ", ["Ↄ"] = "ↄ", ["Ⓐ"] = "ⓐ", ["Ⓑ"] = "ⓑ", ["Ⓒ"] = "ⓒ", ["Ⓓ"] = "ⓓ", ["Ⓔ"] = "ⓔ", ["Ⓕ"] = "ⓕ", ["Ⓖ"] = "ⓖ", ["Ⓗ"] = "ⓗ", ["Ⓘ"] = "ⓘ", ["Ⓙ"] = "ⓙ", ["Ⓚ"] = "ⓚ", ["Ⓛ"] = "ⓛ", ["Ⓜ"] = "ⓜ", ["Ⓝ"] = "ⓝ", ["Ⓞ"] = "ⓞ", ["Ⓟ"] = "ⓟ", ["Ⓠ"] = "ⓠ", ["Ⓡ"] = "ⓡ", ["Ⓢ"] = "ⓢ", ["Ⓣ"] = "ⓣ", ["Ⓤ"] = "ⓤ", ["Ⓥ"] = "ⓥ", ["Ⓦ"] = "ⓦ", ["Ⓧ"] = "ⓧ", ["Ⓨ"] = "ⓨ", ["Ⓩ"] = "ⓩ", ["Ⰰ"] = "ⰰ", ["Ⰱ"] = "ⰱ", ["Ⰲ"] = "ⰲ", ["Ⰳ"] = "ⰳ", ["Ⰴ"] = "ⰴ", ["Ⰵ"] = "ⰵ", ["Ⰶ"] = "ⰶ", ["Ⰷ"] = "ⰷ", ["Ⰸ"] = "ⰸ", ["Ⰹ"] = "ⰹ", ["Ⰺ"] = "ⰺ", ["Ⰻ"] = "ⰻ", ["Ⰼ"] = "ⰼ", ["Ⰽ"] = "ⰽ", ["Ⰾ"] = "ⰾ", ["Ⰿ"] = "ⰿ", ["Ⱀ"] = "ⱀ", ["Ⱁ"] = "ⱁ", ["Ⱂ"] = "ⱂ", ["Ⱃ"] = "ⱃ", ["Ⱄ"] = "ⱄ", ["Ⱅ"] = "ⱅ", ["Ⱆ"] = "ⱆ", ["Ⱇ"] = "ⱇ", ["Ⱈ"] = "ⱈ", ["Ⱉ"] = "ⱉ", ["Ⱊ"] = "ⱊ", ["Ⱋ"] = "ⱋ", ["Ⱌ"] = "ⱌ", ["Ⱍ"] = "ⱍ", ["Ⱎ"] = "ⱎ", ["Ⱏ"] = "ⱏ", ["Ⱐ"] = "ⱐ", ["Ⱑ"] = "ⱑ", ["Ⱒ"] = "ⱒ", ["Ⱓ"] = "ⱓ", ["Ⱔ"] = "ⱔ", ["Ⱕ"] = "ⱕ", ["Ⱖ"] = "ⱖ", ["Ⱗ"] = "ⱗ", ["Ⱘ"] = "ⱘ", ["Ⱙ"] = "ⱙ", ["Ⱚ"] = "ⱚ", ["Ⱛ"] = "ⱛ", ["Ⱜ"] = "ⱜ", ["Ⱝ"] = "ⱝ", ["Ⱞ"] = "ⱞ", ["Ⱡ"] = "ⱡ", ["Ɫ"] = "ɫ", ["Ᵽ"] = "ᵽ", ["Ɽ"] = "ɽ", ["Ⱨ"] = "ⱨ", ["Ⱪ"] = "ⱪ", ["Ⱬ"] = "ⱬ", ["Ⱶ"] = "ⱶ", ["Ⲁ"] = "ⲁ", ["Ⲃ"] = "ⲃ", ["Ⲅ"] = "ⲅ", ["Ⲇ"] = "ⲇ", ["Ⲉ"] = "ⲉ", ["Ⲋ"] = "ⲋ", ["Ⲍ"] = "ⲍ", ["Ⲏ"] = "ⲏ", ["Ⲑ"] = "ⲑ", ["Ⲓ"] = "ⲓ", ["Ⲕ"] = "ⲕ", ["Ⲗ"] = "ⲗ", ["Ⲙ"] = "ⲙ", ["Ⲛ"] = "ⲛ", ["Ⲝ"] = "ⲝ", ["Ⲟ"] = "ⲟ", ["Ⲡ"] = "ⲡ", ["Ⲣ"] = "ⲣ", ["Ⲥ"] = "ⲥ", ["Ⲧ"] = "ⲧ", ["Ⲩ"] = "ⲩ", ["Ⲫ"] = "ⲫ", ["Ⲭ"] = "ⲭ", ["Ⲯ"] = "ⲯ", ["Ⲱ"] = "ⲱ", ["Ⲳ"] = "ⲳ", ["Ⲵ"] = "ⲵ", ["Ⲷ"] = "ⲷ", ["Ⲹ"] = "ⲹ", ["Ⲻ"] = "ⲻ", ["Ⲽ"] = "ⲽ", ["Ⲿ"] = "ⲿ", ["Ⳁ"] = "ⳁ", ["Ⳃ"] = "ⳃ", ["Ⳅ"] = "ⳅ", ["Ⳇ"] = "ⳇ", ["Ⳉ"] = "ⳉ", ["Ⳋ"] = "ⳋ", ["Ⳍ"] = "ⳍ", ["Ⳏ"] = "ⳏ", ["Ⳑ"] = "ⳑ", ["Ⳓ"] = "ⳓ", ["Ⳕ"] = "ⳕ", ["Ⳗ"] = "ⳗ", ["Ⳙ"] = "ⳙ", ["Ⳛ"] = "ⳛ", ["Ⳝ"] = "ⳝ", ["Ⳟ"] = "ⳟ", ["Ⳡ"] = "ⳡ", ["Ⳣ"] = "ⳣ", ["A"] = "a", ["B"] = "b", ["C"] = "c", ["D"] = "d", ["E"] = "e", ["F"] = "f", ["G"] = "g", ["H"] = "h", ["I"] = "i", ["J"] = "j", ["K"] = "k", ["L"] = "l", ["M"] = "m", ["N"] = "n", ["O"] = "o", ["P"] = "p", ["Q"] = "q", ["R"] = "r", ["S"] = "s", ["T"] = "t", ["U"] = "u", ["V"] = "v", ["W"] = "w", ["X"] = "x", ["Y"] = "y", ["Z"] = "z", ["𐐀"] = "𐐨", ["𐐁"] = "𐐩", ["𐐂"] = "𐐪", ["𐐃"] = "𐐫", ["𐐄"] = "𐐬", ["𐐅"] = "𐐭", ["𐐆"] = "𐐮", ["𐐇"] = "𐐯", ["𐐈"] = "𐐰", ["𐐉"] = "𐐱", ["𐐊"] = "𐐲", ["𐐋"] = "𐐳", ["𐐌"] = "𐐴", ["𐐍"] = "𐐵", ["𐐎"] = "𐐶", ["𐐏"] = "𐐷", ["𐐐"] = "𐐸", ["𐐑"] = "𐐹", ["𐐒"] = "𐐺", ["𐐓"] = "𐐻", ["𐐔"] = "𐐼", ["𐐕"] = "𐐽", ["𐐖"] = "𐐾", ["𐐗"] = "𐐿", ["𐐘"] = "𐑀", ["𐐙"] = "𐑁", ["𐐚"] = "𐑂", ["𐐛"] = "𐑃", ["𐐜"] = "𐑄", ["𐐝"] = "𐑅", ["𐐞"] = "𐑆", ["𐐟"] = "𐑇", ["𐐠"] = "𐑈", ["𐐡"] = "𐑉", ["𐐢"] = "𐑊", ["𐐣"] = "𐑋", ["𐐤"] = "𐑌", ["𐐥"] = "𐑍", ["𐐦"] = "𐑎", ["𐐧"] = "𐑏", } ================================================ FILE: gamemode/core/libs/thirdparty/sh_cami.lua ================================================ --[[ CAMI - Common Admin Mod Interface. Copyright 2020 CAMI Contributors Makes admin mods intercompatible and provides an abstract privilege interface for third party addons. Follows the specification on this page: https://github.com/glua/CAMI/blob/master/README.md Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -- Version number in YearMonthDay format. local version = 20211019 if CAMI and CAMI.Version >= version then return end CAMI = CAMI or {} CAMI.Version = version --- @class CAMI_USERGROUP --- defines the charactaristics of a usergroup --- @field Name string @The name of the usergroup --- @field Inherits string @The name of the usergroup this usergroup inherits from --- @field CAMI_Source string @The source specified by the admin mod which registered this usergroup (if any, converted to a string) --- @class CAMI_PRIVILEGE --- defines the charactaristics of a privilege --- @field Name string @The name of the privilege --- @field MinAccess "'user'" | "'admin'" | "'superadmin'" @Default group that should have this privilege --- @field Description string | nil @Optional text describing the purpose of the privilege local CAMI_PRIVILEGE = {} --- Optional function to check if a player has access to this privilege --- (and optionally execute it on another player) --- --- ⚠ **Warning**: This function may not be called by all admin mods --- @param actor GPlayer @The player --- @param target GPlayer | nil @Optional - the target --- @return boolean @If they can or not --- @return string | nil @Optional reason function CAMI_PRIVILEGE:HasAccess(actor, target) end --- Contains the registered CAMI_USERGROUP usergroup structures. --- Indexed by usergroup name. --- @type CAMI_USERGROUP[] local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or { user = { Name = "user", Inherits = "user", CAMI_Source = "Garry's Mod", }, admin = { Name = "admin", Inherits = "user", CAMI_Source = "Garry's Mod", }, superadmin = { Name = "superadmin", Inherits = "admin", CAMI_Source = "Garry's Mod", } } --- Contains the registered CAMI_PRIVILEGE privilege structures. --- Indexed by privilege name. --- @type CAMI_PRIVILEGE[] local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {} --- Registers a usergroup with CAMI. --- --- Use the source parameter to make sure CAMI.RegisterUsergroup function and --- the CAMI.OnUsergroupRegistered hook don't cause an infinite loop --- @param usergroup CAMI_USERGROUP @The structure for the usergroup you want to register --- @param source any @Identifier for your own admin mod. Can be anything. --- @return CAMI_USERGROUP @The usergroup given as an argument function CAMI.RegisterUsergroup(usergroup, source) if source then usergroup.CAMI_Source = tostring(source) end usergroups[usergroup.Name] = usergroup hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source) return usergroup end --- Unregisters a usergroup from CAMI. This will call a hook that will notify --- all other admin mods of the removal. --- --- ⚠ **Warning**: Call only when the usergroup is to be permanently removed. --- --- Use the source parameter to make sure CAMI.UnregisterUsergroup function and --- the CAMI.OnUsergroupUnregistered hook don't cause an infinite loop --- @param usergroupName string @The name of the usergroup. --- @param source any @Identifier for your own admin mod. Can be anything. --- @return boolean @Whether the unregistering succeeded. function CAMI.UnregisterUsergroup(usergroupName, source) if not usergroups[usergroupName] then return false end local usergroup = usergroups[usergroupName] usergroups[usergroupName] = nil hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source) return true end --- Retrieves all registered usergroups. --- @return CAMI_USERGROUP[] @Usergroups indexed by their names. function CAMI.GetUsergroups() return usergroups end --- Receives information about a usergroup. --- @param usergroupName string --- @return CAMI_USERGROUP | nil @Returns nil when the usergroup does not exist. function CAMI.GetUsergroup(usergroupName) return usergroups[usergroupName] end --- Checks to see if potentialAncestor is an ancestor of usergroupName. --- All usergroups are ancestors of themselves. --- --- Examples: --- * `user` is an ancestor of `admin` and also `superadmin` --- * `admin` is an ancestor of `superadmin`, but not `user` --- @param usergroupName string @The usergroup to query --- @param potentialAncestor string @The ancestor to query --- @return boolean @Whether usergroupName inherits potentialAncestor. function CAMI.UsergroupInherits(usergroupName, potentialAncestor) repeat if usergroupName == potentialAncestor then return true end usergroupName = usergroups[usergroupName] and usergroups[usergroupName].Inherits or usergroupName until not usergroups[usergroupName] or usergroups[usergroupName].Inherits == usergroupName -- One can only be sure the usergroup inherits from user if the -- usergroup isn't registered. return usergroupName == potentialAncestor or potentialAncestor == "user" end --- Find the base group a usergroup inherits from. --- --- This function traverses down the inheritence chain, so for example if you have --- `user` -> `group1` -> `group2` --- this function will return `user` if you pass it `group2`. --- --- ℹ **NOTE**: All usergroups must eventually inherit either user, admin or superadmin. --- @param usergroupName string @The name of the usergroup --- @return "'user'" | "'admin'" | "'superadmin'" @The name of the root usergroup function CAMI.InheritanceRoot(usergroupName) if not usergroups[usergroupName] then return end local inherits = usergroups[usergroupName].Inherits while inherits ~= usergroups[usergroupName].Inherits do usergroupName = usergroups[usergroupName].Inherits end return usergroupName end --- Registers an addon privilege with CAMI. --- --- ⚠ **Warning**: This should only be used by addons. Admin mods must *NOT* --- register their privileges using this function. --- @param privilege CAMI_PRIVILEGE --- @return CAMI_PRIVILEGE @The privilege given as argument. function CAMI.RegisterPrivilege(privilege) privileges[privilege.Name] = privilege hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege) return privilege end --- Unregisters a privilege from CAMI. --- This will call a hook that will notify any admin mods of the removal. --- --- ⚠ **Warning**: Call only when the privilege is to be permanently removed. --- @param privilegeName string @The name of the privilege. --- @return boolean @Whether the unregistering succeeded. function CAMI.UnregisterPrivilege(privilegeName) if not privileges[privilegeName] then return false end local privilege = privileges[privilegeName] privileges[privilegeName] = nil hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege) return true end --- Retrieves all registered privileges. --- @return CAMI_PRIVILEGE[] @All privileges indexed by their names. function CAMI.GetPrivileges() return privileges end --- Receives information about a privilege. --- @param privilegeName string --- @return CAMI_PRIVILEGE | nil function CAMI.GetPrivilege(privilegeName) return privileges[privilegeName] end -- Default access handler local defaultAccessHandler = {["CAMI.PlayerHasAccess"] = function(_, actorPly, privilegeName, callback, targetPly, extraInfoTbl) -- The server always has access in the fallback if not IsValid(actorPly) then return callback(true, "Fallback.") end local priv = privileges[privilegeName] local fallback = extraInfoTbl and ( not extraInfoTbl.Fallback and actorPly:IsAdmin() or extraInfoTbl.Fallback == "user" and true or extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin()) if not priv then return callback(fallback, "Fallback.") end local hasAccess = priv.MinAccess == "user" or priv.MinAccess == "admin" and actorPly:IsAdmin() or priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin() if hasAccess and priv.HasAccess then hasAccess = priv:HasAccess(actorPly, targetPly) end callback(hasAccess, "Fallback.") end, ["CAMI.SteamIDHasAccess"] = function(_, _, _, callback) callback(false, "No information available.") end } --- @class CAMI_ACCESS_EXTRA_INFO --- @field Fallback "'user'" | "'admin'" | "'superadmin'" @Fallback status for if the privilege doesn't exist. Defaults to `admin`. --- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have. --- @field CommandArguments table @Extra arguments that were given to the privilege command. --- Checks if a player has access to a privilege --- (and optionally can execute it on targetPly) --- --- This function is designed to be asynchronous but will be invoked --- synchronously if no callback is passed. --- --- ⚠ **Warning**: If the currently installed admin mod does not support --- synchronous queries, this function will throw an error! --- @param actorPly GPlayer @The player to query --- @param privilegeName string @The privilege to query --- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer, or nil for synchronous --- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban) --- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod --- @return boolean | nil @Synchronous only - if the player has the privilege --- @return string | nil @Synchronous only - optional reason from admin mod function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly, extraInfoTbl) local hasAccess, reason = nil, nil local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly, privilegeName, callback_, targetPly, extraInfoTbl) if callback ~= nil then return end if hasAccess == nil then local err = [[The function CAMI.PlayerHasAccess was used to find out whether Player %s has privilege "%s", but an admin mod did not give an immediate answer!]] error(string.format(err, actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly), privilegeName)) end return hasAccess, reason end --- Get all the players on the server with a certain privilege --- (and optionally who can execute it on targetPly) --- --- ℹ **NOTE**: This is an asynchronous function! --- @param privilegeName string @The privilege to query --- @param callback fun(players: GPlayer[]) @Callback to receive the answer --- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban) --- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly, extraInfoTbl) local allowedPlys = {} local allPlys = player.GetAll() local countdown = #allPlys local function onResult(ply, hasAccess, _) countdown = countdown - 1 if hasAccess then table.insert(allowedPlys, ply) end if countdown == 0 then callback(allowedPlys) end end for _, ply in ipairs(allPlys) do CAMI.PlayerHasAccess(ply, privilegeName, function(...) onResult(ply, ...) end, targetPly, extraInfoTbl) end end --- @class CAMI_STEAM_ACCESS_EXTRA_INFO --- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have. --- @field CommandArguments table @Extra arguments that were given to the privilege command. --- Checks if a (potentially offline) SteamID has access to a privilege --- (and optionally if they can execute it on a target SteamID) --- --- ℹ **NOTE**: This is an asynchronous function! --- @param actorSteam string | nil @The SteamID to query --- @param privilegeName string @The privilege to query --- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer --- @param targetSteam string | nil @Optional - target SteamID for if the privilege effects another player (eg kick/ban) --- @param extraInfoTbl CAMI_STEAM_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback, targetSteam, extraInfoTbl) hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam, privilegeName, callback, targetSteam, extraInfoTbl) end --- Signify that your admin mod has changed the usergroup of a player. This --- function communicates to other admin mods what it thinks the usergroup --- of a player should be. --- --- Listen to the hook to receive the usergroup changes of other admin mods. --- @param ply GPlayer @The player for which the usergroup is changed --- @param old string @The previous usergroup of the player. --- @param new string @The new usergroup of the player. --- @param source any @Identifier for your own admin mod. Can be anything. function CAMI.SignalUserGroupChanged(ply, old, new, source) hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source) end --- Signify that your admin mod has changed the usergroup of a disconnected --- player. This communicates to other admin mods what it thinks the usergroup --- of a player should be. --- --- Listen to the hook to receive the usergroup changes of other admin mods. --- @param steamId string @The steam ID of the player for which the usergroup is changed --- @param old string @The previous usergroup of the player. --- @param new string @The new usergroup of the player. --- @param source any @Identifier for your own admin mod. Can be anything. function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source) hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source) end ================================================ FILE: gamemode/core/libs/thirdparty/sh_date.lua ================================================ --------------------------------------------------------------------------------------- -- Module for date and time calculations -- -- Version 2.1.1 -- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com) -- Copyright (C) 2013-2014, by Thijs Schreijer -- Licensed under MIT, http://opensource.org/licenses/MIT -- https://github.com/Tieske/date --[[ The MIT License (MIT) http://opensource.org/licenses/MIT Copyright (c) 2013-2017 Thijs Schreijer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] --[[ CONSTANTS ]]-- local HOURPERDAY = 24 local MINPERHOUR = 60 local MINPERDAY = 1440 -- 24*60 local SECPERMIN = 60 local SECPERHOUR = 3600 -- 60*60 local SECPERDAY = 86400 -- 24*60*60 local TICKSPERSEC = 1000000 local TICKSPERDAY = 86400000000 local TICKSPERHOUR = 3600000000 local TICKSPERMIN = 60000000 local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00 local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00 local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00 local _; --[[ LOCAL ARE FASTER ]]-- local type = type local pairs = pairs local error = error local assert = assert local tonumber = tonumber local tostring = tostring local string = string local math = math local os = os local unpack = unpack or table.unpack local pack = table.pack or function(...) return { n = select('#', ...), ... } end local setmetatable = setmetatable local getmetatable = getmetatable --[[ EXTRA FUNCTIONS ]]-- local fmt = string.format local lwr = string.lower local upr = string.upper local rep = string.rep local len = string.len local sub = string.sub local gsub = string.gsub local gmatch = string.gmatch or string.gfind local find = string.find local ostime = os.time local osdate = os.date local floor = math.floor local ceil = math.ceil local abs = math.abs -- removes the decimal part of a number local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end -- returns the modulo n % d; local function mod(n,d) return n - d*floor(n/d) end -- rounds a number; local function round(n, d) d=d^10 return floor((n*d)+.5)/d end -- rounds a number to whole; local function whole(n)return floor(n+.5)end -- is `str` in string list `tbl`, `ml` is the minimun len local function inlist(str, tbl, ml, tn) local sl = len(str) if sl < (ml or 0) then return nil end str = lwr(str) for k, v in pairs(tbl) do if str == lwr(sub(v, 1, sl)) then if tn then tn[0] = k end return k end end end local function fnil() end local function fret(x)return x;end --[[ DATE FUNCTIONS ]]-- local DATE_EPOCH -- to be set later local sl_weekdays = { [0]="Sunday",[1]="Monday",[2]="Tuesday",[3]="Wednesday",[4]="Thursday",[5]="Friday",[6]="Saturday", [7]="Sun",[8]="Mon",[9]="Tue",[10]="Wed",[11]="Thu",[12]="Fri",[13]="Sat", } local sl_meridian = {[-1]="AM", [1]="PM"} local sl_months = { [00]="January", [01]="February", [02]="March", [03]="April", [04]="May", [05]="June", [06]="July", [07]="August", [08]="September", [09]="October", [10]="November", [11]="December", [12]="Jan", [13]="Feb", [14]="Mar", [15]="Apr", [16]="May", [17]="Jun", [18]="Jul", [19]="Aug", [20]="Sep", [21]="Oct", [22]="Nov", [23]="Dec", } -- added the '.2' to avoid collision, use `fix` to remove local sl_timezone = { [000]="utc", [0.2]="gmt", [300]="est", [240]="edt", [360]="cst", [300.2]="cdt", [420]="mst", [360.2]="mdt", [480]="pst", [420.2]="pdt", } -- set the day fraction resolution local function setticks(t) TICKSPERSEC = t; TICKSPERDAY = SECPERDAY*TICKSPERSEC TICKSPERHOUR= SECPERHOUR*TICKSPERSEC TICKSPERMIN = SECPERMIN*TICKSPERSEC end -- is year y leap year? local function isleapyear(y) -- y must be int! return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0)) end -- day since year 0 local function dayfromyear(y) -- y must be int! return 365*y + floor(y/4) - floor(y/100) + floor(y/400) end -- day number from date, month is zero base local function makedaynum(y, m, d) local mm = mod(mod(m,12) + 10, 12) return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307 --local yy = y + floor(m/12) - floor(mm/10) --return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1) end -- date from day number, month is zero base local function breakdaynum(g) local g = g + 306 local y = floor((10000*g + 14780)/3652425) local d = g - dayfromyear(y) if d < 0 then y = y - 1; d = g - dayfromyear(y) end local mi = floor((100*d + 52)/3060) return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) end --[[ for floats or int32 Lua Number data type local function breakdaynum2(g) local g, n = g + 306; local n400 = floor(g/DI400Y);n = mod(g,DI400Y); local n100 = floor(n/DI100Y);n = mod(n,DI100Y); local n004 = floor(n/DI4Y); n = mod(n,DI4Y); local n001 = floor(n/365); n = mod(n,365); local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0) local d = g - dayfromyear(y) local mi = floor((100*d + 52)/3060) return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) end ]] -- day fraction from time local function makedayfrc(h,r,s,t) return ((h*60 + r)*60 + s)*TICKSPERSEC + t end -- time from day fraction local function breakdayfrc(df) return mod(floor(df/TICKSPERHOUR),HOURPERDAY), mod(floor(df/TICKSPERMIN ),MINPERHOUR), mod(floor(df/TICKSPERSEC ),SECPERMIN), mod(df,TICKSPERSEC) end -- weekday sunday = 0, monday = 1 ... local function weekday(dn) return mod(dn + 1, 7) end -- yearday 0 based ... local function yearday(dn) return dn - dayfromyear((breakdaynum(dn))-1) end -- parse v as a month local function getmontharg(v) local m = tonumber(v); return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2) end -- get daynum of isoweek one of year y local function isow1(y) local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y` local d = weekday(f) d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday return f + (1 - d) end local function isowy(dn) local w1; local y = (breakdaynum(dn)) if dn >= makedaynum(y, 11, 29) then w1 = isow1(y + 1); if dn < w1 then w1 = isow1(y); else y = y + 1; end else w1 = isow1(y); if dn < w1 then w1 = isow1(y-1) y = y - 1 end end return floor((dn-w1)/7)+1, y end local function isoy(dn) local y = (breakdaynum(dn)) return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0)) end local function makedaynum_isoywd(y,w,d) return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1) end --[[ THE DATE MODULE ]]-- local fmtstr = "%x %X"; --#if not DATE_OBJECT_AFX then local date = {} setmetatable(date, date) -- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321 date.version = 20010001 -- 2.1.1 --#end -- not DATE_OBJECT_AFX --[[ THE DATE OBJECT ]]-- local dobj = {} dobj.__index = dobj dobj.__metatable = dobj -- shout invalid arg local function date_error_arg() return error("invalid argument(s)",0) end -- create new date object local function date_new(dn, df) return setmetatable({daynum=dn, dayfrc=df}, dobj) end -- is `v` a date object? local function date_isdobj(v) return (istable(v) and getmetatable(v) == dobj) and v end --#if not NO_LOCAL_TIME_SUPPORT then -- magic year table local date_epoch, yt; local function getequivyear(y) assert(not yt) yt = {} local de, dw, dy = date_epoch:copy() for i = 0, 3000 do de:setyear(de:getyear() + 1, 1, 1) dy = de:getyear() dw = de:getweekday() * (isleapyear(dy) and -1 or 1) if not yt[dw] then yt[dw] = dy end --print(de) if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end return getequivyear(y) end end end -- TimeValue from daynum and dayfrc local function dvtotv(dn, df) return fix(dn - DATE_EPOCH) * SECPERDAY + (df/1000) end -- TimeValue from date and time local function totv(y,m,d,h,r,s) return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s) end -- TimeValue from TimeTable local function tmtotv(tm) return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec) end -- Returns the bias in seconds of utc time daynum and dayfrc local function getbiasutc2(self) local y,m,d = breakdaynum(self.daynum) local h,r,s = breakdayfrc(self.dayfrc) local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time local tml = osdate("*t", tvu) -- get the local TimeTable of tvu if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic y = getequivyear(y) tvu = totv(y,m,d,h,r,s) tml = osdate("*t", tvu) end local tvl = tmtotv(tml) if tvu and tvl then return tvu - tvl, tvu, tvl else return error("failed to get bias from utc time") end end -- Returns the bias in seconds of local time daynum and dayfrc local function getbiasloc2(daynum, dayfrc) local tvu -- extract date and time local y,m,d = breakdaynum(daynum) local h,r,s = breakdayfrc(dayfrc) -- get equivalent TimeTable local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s} -- get equivalent TimeValue local tvl = tmtotv(tml) local function chkutc() tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end tvu = tvud or tvug end chkutc() if not tvu then tml.year = getequivyear(y) tvl = tmtotv(tml) chkutc() end return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl end --#end -- not NO_LOCAL_TIME_SUPPORT --#if not DATE_OBJECT_AFX then -- the date parser local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$ strwalker.__index = strwalker local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end function strwalker:finish() return self.i > self.c end function strwalker:back() self.i = self.e return self end function strwalker:restart() self.i, self.e = 1, 1 return self end function strwalker:match(s) return (find(self.s, s, self.i)) end function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr()) local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i) if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end end local function date_parse(str) local y,m,d, h,r,s, z, w,u, j, e, k, x,v,c, chkfin, dn,df; local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space --local function error_out() print(y,m,d,h,r,s) end local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end local function sety(q) y = y and error_dup() or tonumber(q); end local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end local function setd(q) d = d and error_dup() or tonumber(q) end local function seth(q) h = h and error_dup() or tonumber(q) end local function setr(q) r = r and error_dup() or tonumber(q) end local function sets(q) s = s and error_dup() or tonumber(q) end local function adds(q) s = s + tonumber(q) end local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end local function setz(q) z = (z ~= 0 and z) and error_dup() or q end local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end)) and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds)) or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn)) ) ) then --print(y,m,d,h,r,s,z,w,u,j) sw:restart(); y,m,d,h,r,s,z,w,u,j = nil; repeat -- print(sw:aimchr()) if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time") _ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds) elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits") x, c = tonumber(sw[1]), len(sw[1]) if (x >= 70) or (m and d and (not y)) or (c > 3) then sety( x + ((x >= 100 or c>3)and 0 or 1900) ) else if m then setd(x) else m = x end end elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words") x = sw[1] if inlist(x, sl_months, 2, sw) then if m and (not d) and (not y) then d, m = m, false end setm(mod(sw[0],12)+1) elseif inlist(x, sl_timezone, 2, sw) then c = fix(sw[0]) -- ignore gmt and utc if c ~= 0 then setz(c, x) end elseif inlist(x, sl_weekdays, 2, sw) then k = sw[0] else sw:back() -- am pm bce ad ce bc if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then e = e and error_dup() or -1 elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then e = e and error_dup() or 1 elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then x = lwr(sw[1]) -- there should be hour and it must be correct if (not h) or (h > 12) or (h < 0) then return error_inv() end if x == 'a' and h == 12 then h = 0 end -- am if x == 'p' and h ~= 12 then h = h + 12 end -- pm else error_syn() end end elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}} error_syn("?") end sw("^%s*") until sw:finish() --else print("$Iso(Date|Time|Zone)") end -- if date is given, it must be complete year, month & day if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end -- fix month if m then m = m - 1 end -- fix year if we are on BCE if e and e < 0 and y > 0 then y = 1 - y end -- create date object dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN) --print("Zone",h,r,s,z,m,d,y,df) return date_new(dn, df) -- no need to :normalize(); end local function date_fromtable(v) local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day) local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks) -- atleast there is time or complete date if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0)) end local tmap = { ['number'] = function(v) return date_epoch:copy():addseconds(v) end, ['string'] = function(v) return date_parse(v) end, ['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end, ['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end } local function date_getdobj(v) local o, r = (tmap[type(v)] or fnil)(v); return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj end --#end -- not DATE_OBJECT_AFX local function date_from(...) local arg = pack(...) local y, m, d = fix(arg[1]), getmontharg(arg[2]), fix(arg[3]) local h, r, s, t = tonumber(arg[4] or 0), tonumber(arg[5] or 0), tonumber(arg[6] or 0), tonumber(arg[7] or 0) if y and m and d and h and r and s and t then return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize() else return date_error_arg() end end --[[ THE DATE OBJECT METHODS ]]-- function dobj:normalize() local dn, df = fix(self.daynum), self.dayfrc self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY) return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self) end function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end function dobj:gettime() return breakdayfrc(self.dayfrc) end function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end function dobj:getyearday() return yearday(self.daynum) + 1 end function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ... function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end function dobj:getweeknumber(wdb) local wd, yd = weekday(self.daynum), yearday(self.daynum) if wdb then wdb = tonumber(wdb) if wdb then wd = mod(wd-(wdb-1),7)-- shift the week day base else return date_error_arg() end end return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0)) end function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ... function dobj:getisoweeknumber() return (isowy(self.daynum)) end function dobj:getisoyear() return isoy(self.daynum) end function dobj:getisodate() local w, y = isowy(self.daynum) return y, w, self:getisoweekday() end function dobj:setisoyear(y, w, d) local cy, cw, cd = self:getisodate() if y then cy = fix(tonumber(y))end if w then cw = fix(tonumber(w))end if d then cd = fix(tonumber(d))end if cy and cw and cd then self.daynum = makedaynum_isoywd(cy, cw, cd) return self:normalize() else return date_error_arg() end end function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end function dobj:setyear(y, m, d) local cy, cm, cd = breakdaynum(self.daynum) if y then cy = fix(tonumber(y))end if m then cm = getmontharg(m) end if d then cd = fix(tonumber(d))end if cy and cm and cd then self.daynum = makedaynum(cy, cm, cd) return self:normalize() else return date_error_arg() end end function dobj:setmonth(m, d)return self:setyear(nil, m, d) end function dobj:setday(d) return self:setyear(nil, nil, d) end function dobj:sethours(h, m, s, t) local ch,cm,cs,ck = breakdayfrc(self.dayfrc) ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck) if ch and cm and cs and ck then self.dayfrc = makedayfrc(ch, cm, cs, ck) return self:normalize() else return date_error_arg() end end function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end function dobj:addyears(y, m, d) local cy, cm, cd = breakdaynum(self.daynum) if y then y = fix(tonumber(y))else y = 0 end if m then m = fix(tonumber(m))else m = 0 end if d then d = fix(tonumber(d))else d = 0 end if y and m and d then self.daynum = makedaynum(cy+y, cm+m, cd+d) return self:normalize() else return date_error_arg() end end function dobj:addmonths(m, d) return self:addyears(nil, m, d) end local function dobj_adddayfrc(self,n,pt,pd) n = tonumber(n) if n then local x = floor(n/pd); self.daynum = self.daynum + x; self.dayfrc = self.dayfrc + (n-x*pd)*pt; return self:normalize() else return date_error_arg() end end function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end local tvspec = { -- Abbreviated weekday name (Sun) ['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end, -- Full weekday name (Sunday) ['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end, -- Abbreviated month name (Dec) ['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end, -- Full month name (December) ['%B']=function(self) return sl_months[self:getmonth() - 1] end, -- Year/100 (19, 20, 30) ['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end, -- The day of the month as a number (range 1 - 31) ['%d']=function(self) return fmt("%.2d", self:getday()) end, -- year for ISO 8601 week, from 00 (79) ['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end, -- year for ISO 8601 week, from 0000 (1979) ['%G']=function(self) return fmt("%.4d", self:getisoyear()) end, -- same as %b ['%h']=function(self) return self:fmt0("%b") end, -- hour of the 24-hour day, from 00 (06) ['%H']=function(self) return fmt("%.2d", self:gethours()) end, -- The hour as a number using a 12-hour clock (01 - 12) ['%I']=function(self) return fmt("%.2d", self:getclockhour()) end, -- The day of the year as a number (001 - 366) ['%j']=function(self) return fmt("%.3d", self:getyearday()) end, -- Month of the year, from 01 to 12 ['%m']=function(self) return fmt("%.2d", self:getmonth()) end, -- Minutes after the hour 55 ['%M']=function(self) return fmt("%.2d", self:getminutes())end, -- AM/PM indicator (AM) ['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM) -- The second as a number (59, 20 , 01) ['%S']=function(self) return fmt("%.2d", self:getseconds()) end, -- ISO 8601 day of the week, to 7 for Sunday (7, 1) ['%u']=function(self) return self:getisoweekday() end, -- Sunday week of the year, from 00 (48) ['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end, -- ISO 8601 week of the year, from 01 (48) ['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end, -- The day of the week as a decimal, Sunday being 0 ['%w']=function(self) return self:getweekday() - 1 end, -- Monday week of the year, from 00 (48) ['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end, -- The year as a number without a century (range 00 to 99) ['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end, -- Year with century (2000, 1914, 0325, 0001) ['%Y']=function(self) return fmt("%.4d", self:getyear()) end, -- Time zone offset, the date object is assumed local time (+1000, -0230) ['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end, -- Time zone name, the date object is assumed local time ['%Z']=function(self) return self:gettzname() end, -- Misc -- -- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE) ['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end, -- Seconds including fraction (59.998, 01.123) ['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end, -- percent character % ['%%']=function(self) return "%" end, -- Group Spec -- -- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p" ['%r']=function(self) return self:fmt0("%I:%M:%S %p") end, -- hour:minute, from 01:00 (06:55); same as "%I:%M" ['%R']=function(self) return self:fmt0("%I:%M") end, -- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S" ['%T']=function(self) return self:fmt0("%H:%M:%S") end, -- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y" ['%D']=function(self) return self:fmt0("%m/%d/%y") end, -- year-month-day (1979-12-02); same as "%Y-%m-%d" ['%F']=function(self) return self:fmt0("%Y-%m-%d") end, -- The preferred date and time representation; same as "%x %X" ['%c']=function(self) return self:fmt0("%x %X") end, -- The preferred date representation, same as "%a %b %d %\b" ['%x']=function(self) return self:fmt0("%a %b %d %\b") end, -- The preferred time representation, same as "%H:%M:%\f" ['%X']=function(self) return self:fmt0("%H:%M:%\f") end, -- GroupSpec -- -- Iso format, same as "%Y-%m-%dT%T" ['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end, -- http format, same as "%a, %d %b %Y %T GMT" ['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, -- ctime format, same as "%a %b %d %T GMT %Y" ['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end, -- RFC850 format, same as "%A, %d-%b-%y %T GMT" ['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end, -- RFC1123 format, same as "%a, %d %b %Y %T GMT" ['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, -- asctime format, same as "%a %b %d %T %Y" ['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end, } function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end function dobj:fmt(str) str = str or self.fmtstr or fmtstr return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str) end dobj.format = dobj.fmt function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end function dobj.__sub(a,b) local d1, d2 = date_getdobj(a), date_getdobj(b) local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc) return d0 and d0:normalize() end function dobj.__add(a,b) local d1, d2 = date_getdobj(a), date_getdobj(b) local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc) return d0 and d0:normalize() end function dobj.__concat(a, b) return tostring(a) .. tostring(b) end function dobj:__tostring() return self:fmt() end function dobj:copy() return date_new(self.daynum, self.dayfrc) end --[[ THE LOCAL DATE OBJECT METHODS ]]-- function dobj:tolocal() local dn,df = self.daynum, self.dayfrc local bias = getbiasutc2(self) if bias then -- utc = local + bias; local = utc - bias self.daynum = dn self.dayfrc = df - bias*TICKSPERSEC return self:normalize() else return nil end end function dobj:toutc() local dn,df = self.daynum, self.dayfrc local bias = getbiasloc2(dn, df) if bias then -- utc = local + bias; self.daynum = dn self.dayfrc = df + bias*TICKSPERSEC return self:normalize() else return nil end end function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end function dobj:gettzname() local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc) return tvu and osdate("%Z",tvu) or "" end --#if not DATE_OBJECT_AFX then function date.time(h, r, s, t) h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0) if h and r and s and t then return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t)) else return date_error_arg() end end function date:__call(...) local arg = pack(...) if arg.n > 1 then return (date_from(...)) elseif arg.n == 0 then return (date_getdobj(false)) else local o, r = date_getdobj(arg[1]); return r and o:copy() or o end end date.diff = dobj.__sub function date.isleapyear(v) local y = fix(v); if not y then y = date_getdobj(v) y = y and y:getyear() end return isleapyear(y+0) end function date.epoch() return date_epoch:copy() end function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end -- Internal functions function date.fmt(str) if str then fmtstr = str end; return fmtstr end function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end --#end -- not DATE_OBJECT_AFX local tm = osdate("!*t", 0); if tm then date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0)) -- the distance from our epoch to os epoch in daynum DATE_EPOCH = date_epoch and date_epoch:spandays() else -- error will be raise only if called! date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end}) end function date.serialize(object) return {tostring(object.daynum), tostring(object.dayfrc)} end function date.construct(object) return date_isdobj(object) or (object.daynum and date_new(object.daynum, object.dayfrc) or date_new(object[1], object[2])) end --#if not DATE_OBJECT_AFX then return date --#else --$return date_from --#end ================================================ FILE: gamemode/core/libs/thirdparty/sh_middleclass.lua ================================================ local middleclass = { _VERSION = 'middleclass v4.1.1', _DESCRIPTION = 'Object Orientation for Lua', _URL = 'https://github.com/kikito/middleclass', _LICENSE = [[ MIT LICENSE Copyright (c) 2011 Enrique García Cota Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] } local function _createIndexWrapper(aClass, f) if f == nil then return aClass.__instanceDict else return function(self, name) local value = aClass.__instanceDict[name] if value ~= nil then return value elseif type(f) == "function" then return (f(self, name)) else return f[name] end end end end local function _propagateInstanceMethod(aClass, name, f) f = name == "__index" and _createIndexWrapper(aClass, f) or f aClass.__instanceDict[name] = f for subclass in pairs(aClass.subclasses) do if rawget(subclass.__declaredMethods, name) == nil then _propagateInstanceMethod(subclass, name, f) end end end local function _declareInstanceMethod(aClass, name, f) aClass.__declaredMethods[name] = f if f == nil and aClass.super then f = aClass.super.__instanceDict[name] end _propagateInstanceMethod(aClass, name, f) end local function _tostring(self) return "class " .. self.name end local function _call(self, ...) return self:New(...) end local function _createClass(name, super) local dict = {} dict.__index = dict local aClass = { name = name, super = super, static = {}, __instanceDict = dict, __declaredMethods = {}, subclasses = setmetatable({}, {__mode='k'}) } if super then setmetatable(aClass.static, { __index = function(_,k) local result = rawget(dict,k) if result == nil then return super.static[k] end return result end }) else setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end }) end setmetatable(aClass, { __index = aClass.static, __tostring = _tostring, __call = _call, __newindex = _declareInstanceMethod }) return aClass end local function _includeMixin(aClass, mixin) assert(type(mixin) == 'table', "mixin must be a table") for name,method in pairs(mixin) do if name ~= "Included" and name ~= "static" then aClass[name] = method end end for name,method in pairs(mixin.static or {}) do aClass.static[name] = method end if type(mixin.Included)=="function" then mixin:Included(aClass) end return aClass end local DefaultMixin = { __tostring = function(self) return "instance of " .. tostring(self.class) end, Initialize = function(self, ...) end, IsInstanceOf = function(self, aClass) return type(aClass) == 'table' and type(self) == 'table' and (self.class == aClass or type(self.class) == 'table' and type(self.class.IsSubclassOf) == 'function' and self.class:IsSubclassOf(aClass)) end, static = { Allocate = function(self) assert(type(self) == 'table', "Make sure that you are using 'Class:Allocate' instead of 'Class.Allocate'") return setmetatable({ class = self }, self.__instanceDict) end, New = function(self, ...) assert(type(self) == 'table', "Make sure that you are using 'Class:New' instead of 'Class.New'") local instance = self:Allocate() instance:Initialize(...) return instance end, Subclass = function(self, name) assert(type(self) == 'table', "Make sure that you are using 'Class:Subclass' instead of 'Class.Subclass'") assert(type(name) == "string", "You must provide a name(string) for your class") local subclass = _createClass(name, self) for methodName, f in pairs(self.__instanceDict) do _propagateInstanceMethod(subclass, methodName, f) end subclass.Initialize = function(instance, ...) return self.Initialize(instance, ...) end self.subclasses[subclass] = true self:Subclassed(subclass) return subclass end, Subclassed = function(self, other) end, IsSubclassOf = function(self, other) return type(other) == 'table' and type(self.super) == 'table' and ( self.super == other or self.super:IsSubclassOf(other) ) end, Include = function(self, ...) assert(type(self) == 'table', "Make sure you that you are using 'Class:Include' instead of 'Class.Include'") for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end return self end } } function middleclass.class(name, super) assert(type(name) == 'string', "A name (string) is needed for the new class") return super and super:Subclass(name) or _includeMixin(_createClass(name), DefaultMixin) end setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end }) ix.middleclass = middleclass ================================================ FILE: gamemode/core/libs/thirdparty/sh_pon.lua ================================================ --[[ DEVELOPMENTAL VERSION; VERSION 1.2.2 Copyright thelastpenguin™ You may use this for any purpose as long as: - You don't remove this copyright notice. - You don't claim this to be your own. - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this. If you modify the code for any purpose, the above still applies to the modified code. The author is not held responsible for any damages incured from the use of pon, you use it at your own risk. DATA TYPES SUPPORTED: - tables - k,v - pointers - strings - k,v - pointers - numbers - k,v - booleans- k,v - Vectors - k,v - Angles - k,v - Entities- k,v - Players - k,v CHANGE LOG V 1.1.0 - Added Vehicle, NPC, NextBot, Player, Weapon V 1.2.0 - Added custom handling for k,v tables without any array component. V 1.2.1 - fixed deserialization bug. THANKS TO... - VERCAS for the inspiration. ]] local pon = {}; _G.pon = pon; local type, count = type, table.Count ; local tonumber = tonumber ; local format = string.format; do local type, count = type, table.Count ; local tonumber = tonumber ; local format = string.format; local encode = {}; local tryCache ; local cacheSize = 0; encode['table'] = function( self, tbl, output, cache ) if( cache[ tbl ] )then output[ #output + 1 ] = format('(%x)', cache[tbl] ); return ; else cacheSize = cacheSize + 1; cache[ tbl ] = cacheSize; end local first = next(tbl, nil) local predictedNumeric = 1 local lastKey = nil -- starts with a numeric dealio if first == 1 then output[#output + 1] = '{' for k,v in next, tbl do if k == predictedNumeric then predictedNumeric = predictedNumeric + 1 local tv = type(v) if tv == 'string' then local pid = cache[v] if pid then output[#output + 1] = format('(%x)', pid) else cacheSize = cacheSize + 1 cache[v] = cacheSize self.string(self, v, output, cache) end else self[tv](self, v, output, cache) end else break end end predictedNumeric = predictedNumeric - 1 else predictedNumeric = nil end if predictedNumeric == nil then output[#output + 1] = '[' -- no array component else output[#output + 1] = '~' -- array component came first so shit needs to happen end for k, v in next, tbl, predictedNumeric do local tk, tv = type(k), type(v) -- WRITE KEY if tk == 'string' then local pid = cache[ k ]; if( pid )then output[ #output + 1 ] = format('(%x)', pid ); else cacheSize = cacheSize + 1; cache[ k ] = cacheSize; self.string( self, k, output, cache ); end else self[tk](self, k, output, cache) end -- WRITE VALUE if( tv == 'string' )then local pid = cache[ v ]; if( pid )then output[ #output + 1 ] = format('(%x)', pid ); else cacheSize = cacheSize + 1; cache[ v ] = cacheSize; self.string( self, v, output, cache ); end else self[ tv ]( self, v, output, cache ); end end output[#output + 1] = '}' end -- ENCODE STRING local gsub = string.gsub ; encode['string'] = function( self, str, output ) --if tryCache( str, output ) then return end local estr, count = gsub( str, ";", "\\;"); if( count == 0 )then output[ #output + 1 ] = '\''..str..';'; else output[ #output + 1 ] = '"'..estr..'";'; end end -- ENCODE NUMBER encode['number'] = function( self, num, output ) if num%1 == 0 then if num < 0 then output[ #output + 1 ] = format( 'x%x;', -num ); else output[ #output + 1 ] = format('X%x;', num ); end else output[ #output + 1 ] = tonumber( num )..';'; end end -- ENCODE BOOLEAN encode['boolean'] = function( self, val, output ) output[ #output + 1 ] = val and 't' or 'f' end -- ENCODE VECTOR encode['Vector'] = function( self, val, output ) output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';'); end -- ENCODE ANGLE encode['Angle'] = function( self, val, output ) output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';'); end encode['Entity'] = function( self, val, output ) output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#'); end encode['Player'] = encode['Entity']; encode['Vehicle'] = encode['Entity']; encode['Weapon'] = encode['Entity']; encode['NPC'] = encode['Entity']; encode['NextBot'] = encode['Entity']; encode['PhysObj'] = encode['Entity']; encode['nil'] = function() output[ #output + 1 ] = '?'; end encode.__index = function( key ) ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.'); return encode['nil']; end do local empty, concat = table.Empty, table.concat ; function pon.encode( tbl ) local output = {}; cacheSize = 0; encode[ 'table' ]( encode, tbl, output, {} ); local res = concat( output ); return res; end end end do local tonumber = tonumber ; local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ; local Vector, Angle, Entity = Vector, Angle, Entity ; local decode = {}; decode['{'] = function( self, index, str, cache ) local cur = {}; cache[ #cache + 1 ] = cur; local k, v, tk, tv = 1, nil, nil, nil; while( true )do tv = sub( str, index, index ); if( not tv or tv == '~' )then index = index + 1; break ; end if( tv == '}' )then return index + 1, cur; end -- READ THE VALUE index = index + 1; index, v = self[ tv ]( self, index, str, cache ); cur[ k ] = v; k = k + 1; end while( true )do tk = sub( str, index, index ); if( not tk or tk == '}' )then index = index + 1; break ; end -- READ THE KEY index = index + 1; index, k = self[ tk ]( self, index, str, cache ); -- READ THE VALUE tv = sub( str, index, index ); index = index + 1; index, v = self[ tv ]( self, index, str, cache ); cur[ k ] = v; end return index, cur; end decode['['] = function( self, index, str, cache ) local cur = {}; cache[ #cache + 1 ] = cur; local k, v, tk, tv = 1, nil, nil, nil; while( true )do tk = sub( str, index, index ); if( not tk or tk == '}' )then index = index + 1; break ; end -- READ THE KEY index = index + 1; index, k = self[ tk ]( self, index, str, cache ); if not k then continue end -- READ THE VALUE tv = sub( str, index, index ); index = index + 1; if not self[tv] then print('did not find type: '..tv) end index, v = self[ tv ]( self, index, str, cache ); cur[ k ] = v; end return index, cur; end -- STRING decode['"'] = function( self, index, str, cache ) local finish = find( str, '";', index, true ); local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' ); index = finish + 2; cache[ #cache + 1 ] = res; return index, res; end -- STRING NO ESCAPING NEEDED decode['\''] = function( self, index, str, cache ) local finish = find( str, ';', index, true ); local res = sub( str, index, finish - 1 ) index = finish + 1; cache[ #cache + 1 ] = res; return index, res; end -- NUMBER decode['n'] = function( self, index, str, cache ) index = index - 1; local finish = find( str, ';', index, true ); local num = tonumber( sub( str, index, finish - 1 ) ); index = finish + 1; return index, num; end decode['0'] = decode['n']; decode['1'] = decode['n']; decode['2'] = decode['n']; decode['3'] = decode['n']; decode['4'] = decode['n']; decode['5'] = decode['n']; decode['6'] = decode['n']; decode['7'] = decode['n']; decode['8'] = decode['n']; decode['9'] = decode['n']; decode['-'] = decode['n']; -- positive hex decode['X'] = function( self, index, str, cache ) local finish = find( str, ';', index, true ); local num = tonumber( sub( str, index, finish - 1), 16 ); index = finish + 1; return index, num; end -- negative hex decode['x'] = function( self, index, str, cache ) local finish = find( str, ';', index, true ); local num = -tonumber( sub( str, index, finish - 1), 16 ); index = finish + 1; return index, num; end -- POINTER decode['('] = function( self, index, str, cache ) local finish = find( str, ')', index, true ); local num = tonumber( sub( str, index, finish - 1), 16 ); index = finish + 1; return index, cache[ num ]; end -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO. decode[ 't' ] = function( self, index ) return index, true; end decode[ 'f' ] = function( self, index ) return index, false; end -- VECTOR decode[ 'v' ] = function( self, index, str, cache ) local finish = find( str, ';', index, true ); local vecStr = sub( str, index, finish - 1 ); index = finish + 1; -- update the index. local segs = Explode( ',', vecStr, false ); return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); end -- ANGLE decode[ 'a' ] = function( self, index, str, cache ) local finish = find( str, ';', index, true ); local angStr = sub( str, index, finish - 1 ); index = finish + 1; -- update the index. local segs = Explode( ',', angStr, false ); return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); end -- ENTITY decode[ 'E' ] = function( self, index, str, cache ) if( str[index] == '#' )then index = index + 1; return index, NULL ; else local finish = find( str, ';', index, true ); local num = tonumber( sub( str, index, finish - 1 ) ); index = finish + 1; return index, Entity( num ); end end -- PLAYER decode[ 'P' ] = function( self, index, str, cache ) local finish = find( str, ';', index, true ); local num = tonumber( sub( str, index, finish - 1 ) ); index = finish + 1; return index, Entity( num ) or NULL; end -- NIL decode['?'] = function( self, index, str, cache ) return index + 1, nil; end function pon.decode( data ) local _, res = decode[sub(data,1,1)]( decode, 2, data, {}); return res; end end ================================================ FILE: gamemode/core/libs/thirdparty/sh_tween.lua ================================================ local tween = { _VERSION = 'tween 2.1.1', _DESCRIPTION = 'tweening for lua', _URL = 'https://github.com/kikito/tween.lua', _LICENSE = [[ MIT LICENSE Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] } -- easing -- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits. -- For all easing functions: -- t = time == how much time has to pass for the tweening to complete -- b = begin == starting property value -- c = change == ending - beginning -- d = duration == running time. How much time has passed *right now* local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin -- linear local function linear(t, b, c, d) return c * t / d + b end -- quad local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end local function outQuad(t, b, c, d) t = t / d return -c * t * (t - 2) + b end local function inOutQuad(t, b, c, d) t = t / d * 2 if t < 1 then return c / 2 * pow(t, 2) + b end return -c / 2 * ((t - 1) * (t - 3) - 1) + b end local function outInQuad(t, b, c, d) if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end return inQuad((t * 2) - d, b + c / 2, c / 2, d) end -- cubic local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end local function inOutCubic(t, b, c, d) t = t / d * 2 if t < 1 then return c / 2 * t * t * t + b end t = t - 2 return c / 2 * (t * t * t + 2) + b end local function outInCubic(t, b, c, d) if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end return inCubic((t * 2) - d, b + c / 2, c / 2, d) end -- quart local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end local function inOutQuart(t, b, c, d) t = t / d * 2 if t < 1 then return c / 2 * pow(t, 4) + b end return -c / 2 * (pow(t - 2, 4) - 2) + b end local function outInQuart(t, b, c, d) if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end return inQuart((t * 2) - d, b + c / 2, c / 2, d) end -- quint local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end local function inOutQuint(t, b, c, d) t = t / d * 2 if t < 1 then return c / 2 * pow(t, 5) + b end return c / 2 * (pow(t - 2, 5) + 2) + b end local function outInQuint(t, b, c, d) if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end return inQuint((t * 2) - d, b + c / 2, c / 2, d) end -- sine local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end local function outInSine(t, b, c, d) if t < d / 2 then return outSine(t * 2, b, c / 2, d) end return inSine((t * 2) -d, b + c / 2, c / 2, d) end -- expo local function inExpo(t, b, c, d) if t == 0 then return b end return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 end local function outExpo(t, b, c, d) if t == d then return b + c end return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b end local function inOutExpo(t, b, c, d) if t == 0 then return b end if t == d then return b + c end t = t / d * 2 if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b end local function outInExpo(t, b, c, d) if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end return inExpo((t * 2) - d, b + c / 2, c / 2, d) end -- circ local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end local function inOutCirc(t, b, c, d) t = t / d * 2 if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end t = t - 2 return c / 2 * (sqrt(1 - t * t) + 1) + b end local function outInCirc(t, b, c, d) if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end return inCirc((t * 2) - d, b + c / 2, c / 2, d) end -- elastic local function calculatePAS(p,a,c,d) p, a = p or d * 0.3, a or 0 if a < abs(c) then return p, c, p / 4 end -- p, a, s return p, a, p / (2 * pi) * asin(c/a) -- p,a,s end local function inElastic(t, b, c, d, a, p) local s if t == 0 then return b end t = t / d if t == 1 then return b + c end p,a,s = calculatePAS(p,a,c,d) t = t - 1 return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end local function outElastic(t, b, c, d, a, p) local s if t == 0 then return b end t = t / d if t == 1 then return b + c end p,a,s = calculatePAS(p,a,c,d) return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b end local function inOutElastic(t, b, c, d, a, p) local s if t == 0 then return b end t = t / d * 2 if t == 2 then return b + c end p,a,s = calculatePAS(p,a,c,d) t = t - 1 if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b end local function outInElastic(t, b, c, d, a, p) if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) end -- back local function inBack(t, b, c, d, s) s = s or 1.70158 t = t / d return c * t * t * ((s + 1) * t - s) + b end local function outBack(t, b, c, d, s) s = s or 1.70158 t = t / d - 1 return c * (t * t * ((s + 1) * t + s) + 1) + b end local function inOutBack(t, b, c, d, s) s = (s or 1.70158) * 1.525 t = t / d * 2 if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end t = t - 2 return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b end local function outInBack(t, b, c, d, s) if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end return inBack((t * 2) - d, b + c / 2, c / 2, d, s) end -- bounce local function outBounce(t, b, c, d) t = t / d if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end if t < 2 / 2.75 then t = t - (1.5 / 2.75) return c * (7.5625 * t * t + 0.75) + b elseif t < 2.5 / 2.75 then t = t - (2.25 / 2.75) return c * (7.5625 * t * t + 0.9375) + b end t = t - (2.625 / 2.75) return c * (7.5625 * t * t + 0.984375) + b end local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end local function inOutBounce(t, b, c, d) if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b end local function outInBounce(t, b, c, d) if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end return inBounce((t * 2) - d, b + c / 2, c / 2, d) end tween.easing = { linear = linear, inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad, inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic, inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart, inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint, inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine, inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo, inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc, inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic, inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack, inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce } -- private stuff local function copyTables(destination, keysTable, valuesTable) valuesTable = valuesTable or keysTable local mt = getmetatable(keysTable) if mt and getmetatable(destination) == nil then setmetatable(destination, mt) end for k,v in pairs(keysTable) do if type(v) == 'table' then destination[k] = copyTables({}, v, valuesTable[k]) else destination[k] = valuesTable[k] end end return destination end local function checkSubjectAndTargetRecursively(subject, target, path) path = path or {} local newPath for k,targetValue in pairs(target) do newPath = copyTables({}, path) table.insert(newPath, tostring(k)) if isnumber(targetValue) then assert(isnumber(subject[k]), "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") elseif istable(targetValue) then checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) else assert(isnumber(targetValue), "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") end end end local function checkNewParams(duration, subject, target, easing) assert(isnumber(duration) and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) assert(istable(target), "target must be a table. Was " .. tostring(target)) assert(isfunction(easing), "easing must be a function. Was " .. tostring(easing)) checkSubjectAndTargetRecursively(subject, target) end local function getEasingFunction(easing) easing = easing or "linear" if isstring(easing) then local name = easing easing = tween.easing[name] if not isfunction(easing) then error("The easing function name '" .. name .. "' is invalid") end end return easing end local function performEasingOnSubject(subject, target, initial, clock, duration, easing) local t,b,c,d for k,v in pairs(target) do if istable(v) then performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) else t,b,c,d = clock, initial[k], v - initial[k], duration subject[k] = easing(t,b,c,d) end end end local function applyValues(subject, target) for k, v in pairs(target) do if (istable(v)) then applyValues(subject[k], v) else subject[k] = v end end end -- Tween methods local Tween = {} local Tween_mt = {__index = Tween} function Tween:set(clock) assert(isnumber(clock), "clock must be a positive number or 0") self.initial = self.initial or copyTables({}, self.target, self.subject) self.clock = clock if self.clock <= 0 then self.clock = 0 applyValues(self.subject, self.initial) elseif self.clock >= self.duration then -- the tween has expired self.clock = self.duration applyValues(self.subject, self.target) else performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) end return self.clock >= self.duration end function Tween:reset() return self:set(0) end function Tween:update(dt) assert(isnumber(dt), "dt must be a number") return self:set(self.clock + dt) end -- Public interface function tween.new(duration, subject, target, easing) easing = getEasingFunction(easing) checkNewParams(duration, subject, target, easing) return setmetatable({ duration = duration, subject = subject, target = target, easing = easing, clock = 0 }, Tween_mt) end ix.tween = tween ================================================ FILE: gamemode/core/libs/thirdparty/sh_utf8.lua ================================================ -- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $ -- -- Provides UTF-8 aware string functions implemented in pure lua: -- * string.utf8len(s) -- * string.utf8sub(s, i, j) -- * string.utf8reverse(s) -- -- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these -- additional functions are available: -- * string.utf8upper(s) -- * string.utf8lower(s) -- -- All functions behave as their non UTF-8 aware counterparts with the exception -- that UTF-8 characters are used instead of bytes for all units. --[[ Copyright (c) 2006-2007, Kyle Smith All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --]] -- ABNF from RFC 3629 -- -- UTF8-octets = *( UTF8-char ) -- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 -- UTF8-1 = %x00-7F -- UTF8-2 = %xC2-DF UTF8-tail -- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / -- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) -- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / -- %xF4 %x80-8F 2( UTF8-tail ) -- UTF8-tail = %x80-BF -- ix.util.Include("data/sh_utf8_casemap.lua") -- returns the number of bytes used by the UTF-8 character at byte i in s -- also doubles as a UTF-8 character validator local function utf8charbytes (s, i) -- argument defaults i = i or 1 -- argument checking if not isstring(s) then error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")") end if not isnumber(i) then error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")") end local c = s:byte(i) -- determine bytes needed for character, based on RFC 3629 -- validate byte 1 if c > 0 and c <= 127 then -- UTF8-1 return 1 elseif c >= 194 and c <= 223 then -- UTF8-2 local c2 = s:byte(i + 1) if not c2 then error("UTF-8 string terminated early") end -- validate byte 2 if c2 < 128 or c2 > 191 then error("Invalid UTF-8 character") end return 2 elseif c >= 224 and c <= 239 then -- UTF8-3 local c2 = s:byte(i + 1) local c3 = s:byte(i + 2) if not c2 or not c3 then error("UTF-8 string terminated early") end -- validate byte 2 if c == 224 and (c2 < 160 or c2 > 191) then error("Invalid UTF-8 character") elseif c == 237 and (c2 < 128 or c2 > 159) then error("Invalid UTF-8 character") elseif c2 < 128 or c2 > 191 then error("Invalid UTF-8 character") end -- validate byte 3 if c3 < 128 or c3 > 191 then error("Invalid UTF-8 character") end return 3 elseif c >= 240 and c <= 244 then -- UTF8-4 local c2 = s:byte(i + 1) local c3 = s:byte(i + 2) local c4 = s:byte(i + 3) if not c2 or not c3 or not c4 then error("UTF-8 string terminated early") end -- validate byte 2 if c == 240 and (c2 < 144 or c2 > 191) then error("Invalid UTF-8 character") elseif c == 244 and (c2 < 128 or c2 > 143) then error("Invalid UTF-8 character") elseif c2 < 128 or c2 > 191 then error("Invalid UTF-8 character") end -- validate byte 3 if c3 < 128 or c3 > 191 then error("Invalid UTF-8 character") end -- validate byte 4 if c4 < 128 or c4 > 191 then error("Invalid UTF-8 character") end return 4 else error("Invalid UTF-8 character") end end -- returns the number of characters in a UTF-8 string local function utf8len (s) -- argument checking if not isstring(s) then error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")") end local pos = 1 local bytes = s:len() local len = 0 while pos <= bytes do len = len + 1 pos = pos + utf8charbytes(s, pos) end return len end -- install in the string library if not string.utf8bytes then string.utf8bytes = utf8charbytes end -- install in the string library if not string.utf8len then string.utf8len = utf8len end -- functions identically to string.sub except that i and j are UTF-8 characters -- instead of bytes local function utf8sub (s, i, j) -- argument defaults j = j or -1 -- argument checking if not isstring(s) then error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")") end if not isnumber(i) then error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")") end if not isnumber(j) then error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")") end local pos = 1 local bytes = s:len() local len = 0 -- only set l if i or j is negative local l = (i >= 0 and j >= 0) or s:utf8len() local startChar = (i >= 0) and i or l + i + 1 local endChar = (j >= 0) and j or l + j + 1 -- can't have start before end! if startChar > endChar then return "" end -- byte offsets to pass to string.sub local startByte, endByte = 1, bytes while pos <= bytes do len = len + 1 if len == startChar then startByte = pos end pos = pos + utf8charbytes(s, pos) if len == endChar then endByte = pos - 1 break end end return s:sub(startByte, endByte) end -- install in the string library if not string.utf8sub then string.utf8sub = utf8sub end -- replace UTF-8 characters based on a mapping table local function utf8replace (s, mapping) -- argument checking if not isstring(s) then error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")") end if not istable(mapping) then error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")") end local pos = 1 local bytes = s:len() local charbytes local newstr = "" while pos <= bytes do charbytes = utf8charbytes(s, pos) local c = s:sub(pos, pos + charbytes - 1) newstr = newstr .. (mapping[c] or c) pos = pos + charbytes end return newstr end -- identical to string.upper except it knows about unicode simple case conversions local function utf8upper (s) return utf8replace(s, utf8_lc_uc) end -- install in the string library if not string.utf8upper and utf8_lc_uc then string.utf8upper = utf8upper end -- identical to string.lower except it knows about unicode simple case conversions local function utf8lower (s) return utf8replace(s, utf8_uc_lc) end -- install in the string library if not string.utf8lower and utf8_uc_lc then string.utf8lower = utf8lower end -- identical to string.reverse except that it supports UTF-8 local function utf8reverse (s) -- argument checking if not isstring(s) then error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")") end local bytes = s:len() local pos = bytes local charbytes local newstr = "" while pos > 0 do c = s:byte(pos) while c >= 128 and c <= 191 do pos = pos - 1 c = s:byte(pos) end charbytes = utf8charbytes(s, pos) newstr = newstr .. s:sub(pos, pos + charbytes - 1) pos = pos - 1 end return newstr end -- install in the string library if not string.utf8reverse then string.utf8reverse = utf8reverse end ================================================ FILE: gamemode/core/libs/thirdparty/sh_yaml.lua ================================================ --[[ (The MIT License) Copyright (c) 2017 Dominic Letz dominicletz@exosite.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] local table_print_value table_print_value = function(value, indent, done) indent = indent or 0 done = done or {} if istable(value) and not done [value] then done [value] = true local list = {} for key in pairs (value) do list[#list + 1] = key end table.sort(list, function(a, b) return tostring(a) < tostring(b) end) local last = list[#list] local rep = "{\n" local comma for _, key in ipairs (list) do if key == last then comma = '' else comma = ',' end local keyRep if isnumber(key) then keyRep = key else keyRep = string.format("%q", tostring(key)) end rep = rep .. string.format( "%s[%s] = %s%s\n", string.rep(" ", indent + 2), keyRep, table_print_value(value[key], indent + 2, done), comma ) end rep = rep .. string.rep(" ", indent) -- indent it rep = rep .. "}" done[value] = false return rep elseif isstring(value) then return string.format("%q", value) else return tostring(value) end end local table_print = function(tt) print('return '..table_print_value(tt)) end local table_clone = function(t) local clone = {} for k,v in pairs(t) do clone[k] = v end return clone end local string_trim = function(s, what) what = what or " " return s:gsub("^[" .. what .. "]*(.-)["..what.."]*$", "%1") end local push = function(stack, item) stack[#stack + 1] = item end local pop = function(stack) local item = stack[#stack] stack[#stack] = nil return item end local context = function (str) if not isstring(str) then return "" end str = str:sub(0,25):gsub("\n","\\n"):gsub("\"","\\\""); return ", near \"" .. str .. "\"" end local Parser = {} function Parser.new (self, tokens) self.tokens = tokens self.parse_stack = {} self.refs = {} self.current = 0 return self end local exports = {version = "1.2"} local word = function(w) return "^("..w..")([%s$%c])" end local tokens = { {"comment", "^#[^\n]*"}, {"indent", "^\n( *)"}, {"space", "^ +"}, {"true", word("enabled"), const = true, value = true}, {"true", word("true"), const = true, value = true}, {"true", word("yes"), const = true, value = true}, {"true", word("on"), const = true, value = true}, {"false", word("disabled"), const = true, value = false}, {"false", word("false"), const = true, value = false}, {"false", word("no"), const = true, value = false}, {"false", word("off"), const = true, value = false}, {"null", word("null"), const = true, value = nil}, {"null", word("Null"), const = true, value = nil}, {"null", word("NULL"), const = true, value = nil}, {"null", word("~"), const = true, value = nil}, {"id", "^\"([^\"]-)\" *(:[%s%c])"}, {"id", "^'([^']-)' *(:[%s%c])"}, {"string", "^\"([^\"]-)\"", force_text = true}, {"string", "^'([^']-)'", force_text = true}, {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)"}, {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)"}, {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)"}, {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)"}, {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)"}, {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)"}, {"doc", "^%-%-%-[^%c]*"}, {",", "^,"}, {"string", "^%b{} *[^,%c]+", noinline = true}, {"{", "^{"}, {"}", "^}"}, {"string", "^%b[] *[^,%c]+", noinline = true}, {"[", "^%["}, {"]", "^%]"}, {"-", "^%-"}, {":", "^:"}, {"pipe", "^(|)(%d*[+%-]?)", sep = "\n"}, {"pipe", "^(>)(%d*[+%-]?)", sep = " "}, {"id", "^([%w][%w %-_]*)(:[%s%c])"}, {"string", "^[^%c]+", noinline = true}, {"string", "^[^,%c ]+"} }; exports.tokenize = function (str) local token local row = 0 local ignore local indents = 0 local lastIndents local stack = {} local indentAmount = 0 local inline = false str = str:gsub("\r\n","\010") while #str > 0 do for i in ipairs(tokens) do local captures = {} if not inline or tokens[i].noinline == nil then captures = {str:match(tokens[i][2])} end if #captures > 0 then captures.input = str:sub(0, 25) token = table_clone(tokens[i]) token[2] = captures local str2 = str:gsub(tokens[i][2], "", 1) token.raw = str:sub(1, #str - #str2) str = str2 if token[1] == "{" or token[1] == "[" then inline = true elseif token.const then -- Since word pattern contains last char we're re-adding it str = token[2][2] .. str token.raw = token.raw:sub(1, #token.raw - #token[2][2]) elseif token[1] == "id" then -- Since id pattern contains last semi-colon we're re-adding it str = token[2][2] .. str token.raw = token.raw:sub(1, #token.raw - #token[2][2]) -- Trim token[2][1] = string_trim(token[2][1]) elseif token[1] == "string" then -- Finding numbers local snip = token[2][1] if not token.force_text then if snip:match("^(%d+%.%d+)$") or snip:match("^(%d+)$") then token[1] = "number" end end elseif token[1] == "comment" then ignore = true; elseif token[1] == "indent" then row = row + 1 inline = false lastIndents = indents if indentAmount == 0 then indentAmount = #token[2][1] end if indentAmount ~= 0 then indents = (#token[2][1] / indentAmount); else indents = 0 end if indents == lastIndents then ignore = true; elseif indents > lastIndents + 2 then error("SyntaxError: invalid indentation, got " .. tostring(indents) .. " instead of " .. tostring(lastIndents) .. context(token[2].input)) elseif indents > lastIndents + 1 then push(stack, token) elseif indents < lastIndents then local input = token[2].input token = {"dedent", {"", input = ""}} token.input = input while lastIndents > indents + 1 do lastIndents = lastIndents - 1 push(stack, token) end end end -- if token[1] == XXX token.row = row break end -- if #captures > 0 end if not ignore then if token then push(stack, token) token = nil else error("SyntaxError " .. context(str)) end end ignore = false; end return stack end Parser.peek = function (self, offset) offset = offset or 1 return self.tokens[offset + self.current] end Parser.advance = function (self) self.current = self.current + 1 return self.tokens[self.current] end Parser.advanceValue = function (self) return self:advance()[2][1] end Parser.accept = function (self, type) if self:peekType(type) then return self:advance() end end Parser.expect = function (self, type, msg) return self:accept(type) or error(msg .. context(self:peek()[1].input)) end Parser.expectDedent = function (self, msg) return self:accept("dedent") or (self:peek() == nil) or error(msg .. context(self:peek()[2].input)) end Parser.peekType = function (self, val, offset) return self:peek(offset) and self:peek(offset)[1] == val end Parser.ignore = function (self, items) local advanced repeat advanced = false for _,v in pairs(items) do if self:peekType(v) then self:advance() advanced = true end end until advanced == false end Parser.ignoreSpace = function (self) self:ignore{"space"} end Parser.ignoreWhitespace = function (self) self:ignore{"space", "indent", "dedent"} end Parser.parse = function (self) local ref = nil if self:peekType("string") and not self:peek().force_text then local char = self:peek()[2][1]:sub(1,1) if char == "&" then ref = self:peek()[2][1]:sub(2) self:advanceValue() self:ignoreSpace() elseif char == "*" then ref = self:peek()[2][1]:sub(2) return self.refs[ref] end end local result local c = { indent = self:accept("indent") and 1 or 0, token = self:peek() } push(self.parse_stack, c) if c.token[1] == "doc" then result = self:parseDoc() elseif c.token[1] == "-" then result = self:parseList() elseif c.token[1] == "{" then result = self:parseInlineHash() elseif c.token[1] == "[" then result = self:parseInlineList() elseif c.token[1] == "id" then result = self:parseHash() elseif c.token[1] == "string" then result = self:parseString("\n") elseif c.token[1] == "timestamp" then result = self:parseTimestamp() elseif c.token[1] == "number" then result = tonumber(self:advanceValue()) elseif c.token[1] == "pipe" then result = self:parsePipe() elseif c.token.const == true then self:advanceValue(); result = c.token.value else error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input)) end pop(self.parse_stack) while c.indent > 0 do c.indent = c.indent - 1 local term = "term "..c.token[1]..": '"..c.token[2][1].."'" self:expectDedent("last ".. term .." is not properly dedented") end if ref then self.refs[ref] = result end return result end Parser.parseDoc = function (self) self:accept("doc") return self:parse() end Parser.inline = function (self) local current = self:peek(0) if not current then return {}, 0 end local inline = {} local i = 0 while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do inline[self:peek(i)[1]] = true i = i - 1 end return inline, -i end Parser.isInline = function (self) local _, i = self:inline() return i > 0 end Parser.parent = function(self, level) level = level or 1 return self.parse_stack[#self.parse_stack - level] end Parser.parentType = function(self, type, level) return self:parent(level) and self:parent(level).token[1] == type end Parser.parseString = function (self) if self:isInline() then local result = self:advanceValue() --[[ - a: this looks flowing: but is no: string --]] local types = self:inline() if types["id"] and types["-"] then if not self:peekType("indent") or not self:peekType("indent", 2) then return result end end --[[ a: 1 b: this is a flowing string example c: 3 --]] if self:peekType("indent") then self:expect("indent", "text block needs to start with indent") local addtl = self:accept("indent") result = result .. "\n" .. self:parseTextBlock("\n") self:expectDedent("text block ending dedent missing") if addtl then self:expectDedent("text block ending dedent missing") end end return result else --[[ a: 1 b: this is also a flowing string example c: 3 --]] return self:parseTextBlock("\n") end end Parser.parsePipe = function (self) local pipe = self:expect("pipe") self:expect("indent", "text block needs to start with indent") local result = self:parseTextBlock(pipe.sep) self:expectDedent("text block ending dedent missing") return result end Parser.parseTextBlock = function (self, sep) local token = self:advance() local result = string_trim(token.raw, "\n") local indents = 0 while self:peek() ~= nil and ( indents > 0 or not self:peekType("dedent") ) do local newtoken = self:advance() while token.row < newtoken.row do result = result .. sep token.row = token.row + 1 end if newtoken[1] == "indent" then indents = indents + 1 elseif newtoken[1] == "dedent" then indents = indents - 1 else result = result .. string_trim(newtoken.raw, "\n") end end return result end Parser.parseHash = function (self, hash) hash = hash or {} local indents = 0 if self:isInline() then local id = self:advanceValue() self:expect(":", "expected semi-colon after id") self:ignoreSpace() if self:accept("indent") then indents = indents + 1 hash[id] = self:parse() else hash[id] = self:parse() if self:accept("indent") then indents = indents + 1 end end self:ignoreSpace(); end while self:peekType("id") do local id = self:advanceValue() self:expect(":","expected semi-colon after id") self:ignoreSpace() hash[id] = self:parse() self:ignoreSpace(); end while indents > 0 do self:expectDedent("expected dedent") indents = indents - 1 end return hash end Parser.parseInlineHash = function (self) local id local hash = {} local i = 0 self:accept("{") while not self:accept("}") do self:ignoreSpace() if i > 0 then self:expect(",","expected comma") end self:ignoreWhitespace() if self:peekType("id") then id = self:advanceValue() if id then self:expect(":","expected semi-colon after id") self:ignoreSpace() hash[id] = self:parse() self:ignoreWhitespace() end end i = i + 1 end return hash end Parser.parseList = function (self) local list = {} while self:accept("-") do self:ignoreSpace() list[#list + 1] = self:parse() self:ignoreSpace() end return list end Parser.parseInlineList = function (self) local list = {} local i = 0 self:accept("[") while not self:accept("]") do self:ignoreSpace() if i > 0 then self:expect(",","expected comma") end self:ignoreSpace() list[#list + 1] = self:parse() self:ignoreSpace() i = i + 1 end return list end Parser.parseTimestamp = function (self) local capture = self:advance()[2] return os.time{ year = capture[1], month = capture[2], day = capture[3], hour = capture[4] or 0, min = capture[5] or 0, sec = capture[6] or 0 } end exports.Eval = function (str) return Parser:new(exports.tokenize(str)):parse() end exports.Read = function(file_name) if file.Exists(file_name, 'GAME') then local local_name = file_name:gsub('%.y([a]?)ml', '.local.y%1ml') if file.Exists(local_name, 'GAME') then file_name = local_name end return Parser:new(exports.tokenize(file.Read(file_name, 'GAME'))):parse() end end exports.Dump = table_print ix.yaml = exports ================================================ FILE: gamemode/core/libs/thirdparty/sv_mysql.lua ================================================ --[[ mysql - 1.0.3 A simple MySQL wrapper for Garry's Mod. Alexander Grist-Hucker http://www.alexgrist.com The MIT License (MIT) Copyright (c) 2014 Alex Grist-Hucker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] mysql = mysql or { module = "sqlite" } local QueueTable = {} local tostring = tostring local table = table --[[ Replacement tables --]] local Replacements = { sqlite = { Create = { {"UNSIGNED ", ""}, {"NOT NULL AUTO_INCREMENT", ""}, -- assuming primary key {"AUTO_INCREMENT", ""}, {"INT%(%d*%)", "INTEGER"}, {"INT ", "INTEGER"} } } } --[[ Phrases --]] local MODULE_NOT_EXIST = "[mysql] The %s module does not exist!\n" --[[ Begin Query Class. --]] local QUERY_CLASS = {} QUERY_CLASS.__index = QUERY_CLASS function QUERY_CLASS:New(tableName, queryType) local newObject = setmetatable({}, QUERY_CLASS) newObject.queryType = queryType newObject.tableName = tableName newObject.selectList = {} newObject.insertList = {} newObject.updateList = {} newObject.createList = {} newObject.whereList = {} newObject.orderByList = {} return newObject end function QUERY_CLASS:Escape(text) return mysql:Escape(tostring(text)) end function QUERY_CLASS:ForTable(tableName) self.tableName = tableName end function QUERY_CLASS:Where(key, value) self:WhereEqual(key, value) end function QUERY_CLASS:WhereEqual(key, value) self.whereList[#self.whereList + 1] = "`"..key.."` = '"..self:Escape(value).."'" end function QUERY_CLASS:WhereNotEqual(key, value) self.whereList[#self.whereList + 1] = "`"..key.."` != '"..self:Escape(value).."'" end function QUERY_CLASS:WhereLike(key, value, format) format = format or "%%%s%%" self.whereList[#self.whereList + 1] = "`"..key.."` LIKE '"..string.format(format, self:Escape(value)).."'" end function QUERY_CLASS:WhereNotLike(key, value, format) format = format or "%%%s%%" self.whereList[#self.whereList + 1] = "`"..key.."` NOT LIKE '"..string.format(format, self:Escape(value)).."'" end function QUERY_CLASS:WhereGT(key, value) self.whereList[#self.whereList + 1] = "`"..key.."` > '"..self:Escape(value).."'" end function QUERY_CLASS:WhereLT(key, value) self.whereList[#self.whereList + 1] = "`"..key.."` < '"..self:Escape(value).."'" end function QUERY_CLASS:WhereGTE(key, value) self.whereList[#self.whereList + 1] = "`"..key.."` >= '"..self:Escape(value).."'" end function QUERY_CLASS:WhereLTE(key, value) self.whereList[#self.whereList + 1] = "`"..key.."` <= '"..self:Escape(value).."'" end function QUERY_CLASS:WhereIn(key, value) value = istable(value) and value or {value} local values = "" local bFirst = true for k, v in pairs(value) do values = values .. (bFirst and "" or ", ") .. "'" .. self:Escape(v) .. "'" bFirst = false end self.whereList[#self.whereList + 1] = "`"..key.."` IN ("..values..")" end function QUERY_CLASS:OrderByDesc(key) self.orderByList[#self.orderByList + 1] = "`"..key.."` DESC" end function QUERY_CLASS:OrderByAsc(key) self.orderByList[#self.orderByList + 1] = "`"..key.."` ASC" end function QUERY_CLASS:Callback(queryCallback) self.callback = queryCallback end function QUERY_CLASS:Select(fieldName) self.selectList[#self.selectList + 1] = "`"..fieldName.."`" end function QUERY_CLASS:Insert(key, value) self.insertList[#self.insertList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"} end function QUERY_CLASS:Update(key, value) self.updateList[#self.updateList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"} end function QUERY_CLASS:Create(key, value) self.createList[#self.createList + 1] = {"`"..key.."`", value} end function QUERY_CLASS:Add(key, value) self.add = {"`"..key.."`", value} end function QUERY_CLASS:Drop(key) self.drop = "`"..key.."`" end function QUERY_CLASS:PrimaryKey(key) self.primaryKey = "`"..key.."`" end function QUERY_CLASS:Limit(value) self.limit = value end function QUERY_CLASS:Offset(value) self.offset = value end local function ApplyQueryReplacements(mode, query) if (!Replacements[mysql.module]) then return query end local result = query local entries = Replacements[mysql.module][mode] for i = 1, #entries do result = string.gsub(result, entries[i][1], entries[i][2]) end return result end local function BuildSelectQuery(queryObj) local queryString = {"SELECT"} if (!istable(queryObj.selectList) or #queryObj.selectList == 0) then queryString[#queryString + 1] = " *" else queryString[#queryString + 1] = " "..table.concat(queryObj.selectList, ", ") end if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " FROM `"..queryObj.tableName.."` " else ErrorNoHalt("[mysql] No table name specified!\n") return end if (istable(queryObj.whereList) and #queryObj.whereList > 0) then queryString[#queryString + 1] = " WHERE " queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ") end if (istable(queryObj.orderByList) and #queryObj.orderByList > 0) then queryString[#queryString + 1] = " ORDER BY " queryString[#queryString + 1] = table.concat(queryObj.orderByList, ", ") end if (isnumber(queryObj.limit)) then queryString[#queryString + 1] = " LIMIT " queryString[#queryString + 1] = queryObj.limit end if (isnumber(queryObj.offset)) then queryString[#queryString + 1] = " OFFSET " queryString[#queryString + 1] = queryObj.offset end return table.concat(queryString) end local function BuildInsertQuery(queryObj, bIgnore) local suffix = (bIgnore and (mysql.module == "sqlite" and "INSERT OR IGNORE INTO" or "INSERT IGNORE INTO") or "INSERT INTO") local queryString = {suffix} local keyList = {} local valueList = {} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end for i = 1, #queryObj.insertList do keyList[#keyList + 1] = queryObj.insertList[i][1] valueList[#valueList + 1] = queryObj.insertList[i][2] end if (#keyList == 0) then return end queryString[#queryString + 1] = " ("..table.concat(keyList, ", ")..")" queryString[#queryString + 1] = " VALUES ("..table.concat(valueList, ", ")..")" return table.concat(queryString) end local function BuildUpdateQuery(queryObj) local queryString = {"UPDATE"} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end if (istable(queryObj.updateList) and #queryObj.updateList > 0) then local updateList = {} queryString[#queryString + 1] = " SET" for i = 1, #queryObj.updateList do updateList[#updateList + 1] = queryObj.updateList[i][1].." = "..queryObj.updateList[i][2] end queryString[#queryString + 1] = " "..table.concat(updateList, ", ") end if (istable(queryObj.whereList) and #queryObj.whereList > 0) then queryString[#queryString + 1] = " WHERE " queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ") end if (isnumber(queryObj.offset)) then queryString[#queryString + 1] = " OFFSET " queryString[#queryString + 1] = queryObj.offset end return table.concat(queryString) end local function BuildDeleteQuery(queryObj) local queryString = {"DELETE FROM"} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end if (istable(queryObj.whereList) and #queryObj.whereList > 0) then queryString[#queryString + 1] = " WHERE " queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ") end if (isnumber(queryObj.limit)) then queryString[#queryString + 1] = " LIMIT " queryString[#queryString + 1] = queryObj.limit end return table.concat(queryString) end local function BuildDropQuery(queryObj) local queryString = {"DROP TABLE"} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end return table.concat(queryString) end local function BuildTruncateQuery(queryObj) local queryString = {"TRUNCATE TABLE"} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end return table.concat(queryString) end local function BuildCreateQuery(queryObj) local queryString = {"CREATE TABLE IF NOT EXISTS"} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end queryString[#queryString + 1] = " (" if (istable(queryObj.createList) and #queryObj.createList > 0) then local createList = {} for i = 1, #queryObj.createList do if (mysql.module == "sqlite") then createList[#createList + 1] = queryObj.createList[i][1].." "..ApplyQueryReplacements("Create", queryObj.createList[i][2]) else createList[#createList + 1] = queryObj.createList[i][1].." "..queryObj.createList[i][2] end end queryString[#queryString + 1] = " "..table.concat(createList, ", ") end if (isstring(queryObj.primaryKey)) then queryString[#queryString + 1] = ", PRIMARY KEY" queryString[#queryString + 1] = " ("..queryObj.primaryKey..")" end queryString[#queryString + 1] = " )" return table.concat(queryString) end local function BuildAlterQuery(queryObj) local queryString = {"ALTER TABLE"} if (isstring(queryObj.tableName)) then queryString[#queryString + 1] = " `"..queryObj.tableName.."`" else ErrorNoHalt("[mysql] No table name specified!\n") return end if (istable(queryObj.add)) then queryString[#queryString + 1] = " ADD "..queryObj.add[1].." "..ApplyQueryReplacements("Create", queryObj.add[2]) elseif (isstring(queryObj.drop)) then if (mysql.module == "sqlite") then ErrorNoHalt("[mysql] Cannot drop columns in sqlite!\n") return end queryString[#queryString + 1] = " DROP COLUMN "..queryObj.drop end return table.concat(queryString) end function QUERY_CLASS:Execute(bQueueQuery) local queryString = nil local queryType = string.lower(self.queryType) if (queryType == "select") then queryString = BuildSelectQuery(self) elseif (queryType == "insert") then queryString = BuildInsertQuery(self) elseif (queryType == "insert ignore") then queryString = BuildInsertQuery(self, true) elseif (queryType == "update") then queryString = BuildUpdateQuery(self) elseif (queryType == "delete") then queryString = BuildDeleteQuery(self) elseif (queryType == "drop") then queryString = BuildDropQuery(self) elseif (queryType == "truncate") then queryString = BuildTruncateQuery(self) elseif (queryType == "create") then queryString = BuildCreateQuery(self) elseif (queryType == "alter") then queryString = BuildAlterQuery(self) end if (isstring(queryString)) then if (!bQueueQuery) then return mysql:RawQuery(queryString, self.callback) else return mysql:Queue(queryString, self.callback) end end end --[[ End Query Class. --]] function mysql:Select(tableName) return QUERY_CLASS:New(tableName, "SELECT") end function mysql:Insert(tableName) return QUERY_CLASS:New(tableName, "INSERT") end function mysql:InsertIgnore(tableName) return QUERY_CLASS:New(tableName, "INSERT IGNORE") end function mysql:Update(tableName) return QUERY_CLASS:New(tableName, "UPDATE") end function mysql:Delete(tableName) return QUERY_CLASS:New(tableName, "DELETE") end function mysql:Drop(tableName) return QUERY_CLASS:New(tableName, "DROP") end function mysql:Truncate(tableName) return QUERY_CLASS:New(tableName, "TRUNCATE") end function mysql:Create(tableName) return QUERY_CLASS:New(tableName, "CREATE") end function mysql:Alter(tableName) return QUERY_CLASS:New(tableName, "ALTER") end local UTF8MB4 = "ALTER DATABASE %s CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci" -- A function to connect to the MySQL database. function mysql:Connect(host, username, password, database, port, socket, flags) port = port or 3306 if (self.module == "mysqloo") then if (!istable(mysqloo)) then require("mysqloo") end if (mysqloo) then if (self.connection and self.connection:ping()) then return end local clientFlag = flags or 0 if (!isstring(socket)) then self.connection = mysqloo.connect(host, username, password, database, port) else self.connection = mysqloo.connect(host, username, password, database, port, socket, clientFlag) end self.connection.onConnected = function(connection) local success, error_message = connection:setCharacterSet("utf8mb4") if (!success) then ErrorNoHalt("Failed to set MySQL encoding!\n") ErrorNoHalt(error_message .. "\n") else self:RawQuery(string.format(UTF8MB4, database)) end mysql:OnConnected() end self.connection.onConnectionFailed = function(database, errorText) mysql:OnConnectionFailed(errorText) end self.connection:connect() timer.Create("mysql.KeepAlive", 300, 0, function() self.connection:ping() end) else ErrorNoHalt(string.format(MODULE_NOT_EXIST, self.module)) end elseif (self.module == "sqlite") then timer.Simple(0, function() mysql:OnConnected() end) end end -- A function to query the MySQL database. function mysql:RawQuery(query, callback, flags, ...) if (self.module == "mysqloo") then local queryObj = self.connection:query(query) queryObj:setOption(mysqloo.OPTION_NAMED_FIELDS) queryObj.onSuccess = function(queryObj, result) if (callback) then local bStatus, value = pcall(callback, result, true, tonumber(queryObj:lastInsert())) if (!bStatus) then error(string.format("[mysql] MySQL Callback Error!\n%s\n", value)) end end end queryObj.onError = function(queryObj, errorText) ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, errorText)) end queryObj:start() elseif (self.module == "sqlite") then local result = sql.Query(query) if (result == false) then error(string.format("[mysql] SQL Query Error!\nQuery: %s\n%s\n", query, sql.LastError())) else if (callback) then local bStatus, value = pcall(callback, result, true, tonumber(sql.QueryValue("SELECT last_insert_rowid()"))) if (!bStatus) then error(string.format("[mysql] SQL Callback Error!\n%s\n", value)) end end end else ErrorNoHalt(string.format("[mysql] Unsupported module \"%s\"!\n", self.module)) end end -- A function to add a query to the queue. function mysql:Queue(queryString, callback) if (isstring(queryString)) then QueueTable[#QueueTable + 1] = {queryString, callback} end end -- A function to escape a string for MySQL. function mysql:Escape(text) if (self.connection) then if (self.module == "mysqloo") then return self.connection:escape(text) end else return sql.SQLStr(text, true) end end -- A function to disconnect from the MySQL database. function mysql:Disconnect() if (self.connection) then if (self.module == "mysqloo") then self.connection:disconnect(true) end end end function mysql:Think() if (#QueueTable > 0) then if (istable(QueueTable[1])) then local queueObj = QueueTable[1] local queryString = queueObj[1] local callback = queueObj[2] if (isstring(queryString)) then self:RawQuery(queryString, callback) end table.remove(QueueTable, 1) end end end -- A function to set the module that should be used. function mysql:SetModule(moduleName) self.module = moduleName end -- Called when the database connects sucessfully. function mysql:OnConnected() MsgC(Color(25, 235, 25), "[mysql] Connected to the database!\n") hook.Run("DatabaseConnected") end -- Called when the database connection fails. function mysql:OnConnectionFailed(errorText) ErrorNoHalt(string.format("[mysql] Unable to connect to the database!\n%s\n", errorText)) hook.Run("DatabaseConnectionFailed", errorText) end -- A function to check whether or not the module is connected to a database. function mysql:IsConnected() return self.module == "mysqloo" and (self.connection and self.connection:ping()) or self.module == "sqlite" end return mysql ================================================ FILE: gamemode/core/meta/sh_character.lua ================================================ --[[-- Contains information about a player's current game state. Characters are a fundamental object type in Helix. They are distinct from players, where players are the representation of a person's existence in the server that owns a character, and their character is their currently selected persona. All the characters that a player owns will be loaded into memory once they connect to the server. Characters are saved during a regular interval, and during specific events (e.g when the owning player switches away from one character to another). They contain all information that is not persistent with the player; names, descriptions, model, currency, etc. For the most part, you'll want to keep all information stored on the character since it will probably be different or change if the player switches to another character. An easy way to do this is to use `ix.char.RegisterVar` to easily create accessor functions for variables that automatically save to the character object. ]] -- @classmod Character local CHAR = ix.meta.character or {} CHAR.__index = CHAR CHAR.id = CHAR.id or 0 CHAR.vars = CHAR.vars or {} -- @todo not this if (!ix.db) then ix.util.Include("../libs/sv_database.lua") end --- Returns a string representation of this character -- @realm shared -- @treturn string String representation -- @usage print(ix.char.loaded[1]) -- > "character[1]" function CHAR:__tostring() return "character["..(self.id or 0).."]" end --- Returns true if this character is equal to another character. Internally, this checks character IDs. -- @realm shared -- @char other Character to compare to -- @treturn bool Whether or not this character is equal to the given character -- @usage print(ix.char.loaded[1] == ix.char.loaded[2]) -- > false function CHAR:__eq(other) return self:GetID() == other:GetID() end --- Returns this character's database ID. This is guaranteed to be unique. -- @realm shared -- @treturn number Unique ID of character function CHAR:GetID() return self.id end if (SERVER) then --- Saves this character's info to the database. -- @realm server -- @func[opt=nil] callback Function to call when the save has completed. -- @usage ix.char.loaded[1]:Save(function() -- print("done!") -- end) -- > done! -- after a moment function CHAR:Save(callback) -- Do not save if the character is for a bot. if (self.isBot) then return end -- Let plugins/schema determine if the character should be saved. local shouldSave = hook.Run("CharacterPreSave", self) if (shouldSave != false) then -- Run a query to save the character to the database. local query = mysql:Update("ix_characters") -- update all character vars for k, v in pairs(ix.char.vars) do if (v.field and self.vars[k] != nil and !v.bSaveLoadInitialOnly) then local value = self.vars[k] query:Update(v.field, istable(value) and util.TableToJSON(value) or tostring(value)) end end query:Where("id", self:GetID()) query:Callback(function() if (callback) then callback() end hook.Run("CharacterPostSave", self) end) query:Execute() end end --- Networks this character's information to make the given player aware of this character's existence. If the receiver is -- not the owner of this character, it will only be sent a limited amount of data (as it does not need anything else). -- This is done automatically by the framework. -- @internal -- @realm server -- @player[opt=nil] receiver Player to send the information to. This will sync to all connected players if set to `nil`. function CHAR:Sync(receiver) -- Broadcast the character information if receiver is not set. if (receiver == nil) then for _, v in player.Iterator() do self:Sync(v) end -- Send all character information if the receiver is the character's owner. elseif (receiver == self.player) then local data = {} for k, v in pairs(self.vars) do if (ix.char.vars[k] != nil and !ix.char.vars[k].bNoNetworking) then data[k] = v end end net.Start("ixCharacterInfo") net.WriteTable(data) net.WriteUInt(self:GetID(), 32) net.WriteUInt(self.player:EntIndex(), 8) net.Send(self.player) else local data = {} for k, v in pairs(ix.char.vars) do if (!v.bNoNetworking and !v.isLocal) then data[k] = self.vars[k] end end net.Start("ixCharacterInfo") net.WriteTable(data) net.WriteUInt(self:GetID(), 32) net.WriteUInt(self.player:EntIndex(), 8) net.Send(receiver) end end -- Sets up the "appearance" related inforomation for the character. --- Applies the character's appearance and synchronizes information to the owning player. -- @realm server -- @internal -- @bool[opt] bNoNetworking Whether or not to sync the character info to other players function CHAR:Setup(bNoNetworking) local client = self:GetPlayer() if (IsValid(client)) then -- Set the faction, model, and character index for the player. local model = self:GetModel() client:SetNetVar("char", self:GetID()) client:SetTeam(self:GetFaction()) client:SetModel(istable(model) and model[1] or model) -- Apply saved body groups. for k, v in pairs(self:GetData("groups", {})) do client:SetBodygroup(k, v) end -- Apply a saved skin. client:SetSkin(self:GetData("skin", 0)) -- Synchronize the character if we should. if (!bNoNetworking) then if (client:IsBot()) then timer.Simple(0.33, function() self:Sync() end) else self:Sync() end for _, v in ipairs(self:GetInventory(true)) do if (istable(v)) then v:AddReceiver(client) v:Sync(client) end end end local id = self:GetID() hook.Run("CharacterLoaded", ix.char.loaded[id]) net.Start("ixCharacterLoaded") net.WriteUInt(id, 32) net.Send(client) self.firstTimeLoaded = true end end --- Forces a player off their current character, and sends them to the character menu to select a character. -- @realm server function CHAR:Kick() -- Kill the player so they are not standing anywhere. local client = self:GetPlayer() client:KillSilent() local steamID = client:SteamID64() local id = self:GetID() local isCurrentChar = self and self:GetID() == id -- Return the player to the character menu. if (self and self.steamID == steamID) then net.Start("ixCharacterKick") net.WriteBool(isCurrentChar) net.Send(client) if (isCurrentChar) then client:SetNetVar("char", nil) client:Spawn() end end end --- Forces a player off their current character, and prevents them from using the character for the specified amount of time. -- @realm server -- @number[opt] time Amount of seconds to ban the character for. If left as `nil`, the character will be banned permanently function CHAR:Ban(time) time = tonumber(time) if (time) then -- If time is provided, adjust it so it becomes the un-ban time. time = os.time() + math.max(math.ceil(time), 60) end -- Mark the character as banned and kick the character back to menu. self:SetData("banned", time or true) self:Kick() end end --- Returns the player that owns this character. -- @realm shared -- @treturn player Player that owns this character function CHAR:GetPlayer() -- Set the player from entity index. if (isnumber(self.player)) then local client = Entity(self.player) if (IsValid(client)) then self.player = client return client end -- Return the player from cache. elseif (IsValid(self.player)) then return self.player -- Search for which player owns this character. elseif (self.steamID) then local steamID = self.steamID for _, v in player.Iterator() do if (v:SteamID64() == steamID) then self.player = v return v end end end end -- Sets up a new character variable. function ix.char.RegisterVar(key, data) -- Store information for the variable. ix.char.vars[key] = data data.index = data.index or table.Count(ix.char.vars) local upperName = key:sub(1, 1):upper() .. key:sub(2) if (SERVER) then if (data.field) then ix.db.AddToSchema("ix_characters", data.field, data.fieldType or ix.type.string) end -- Provide functions to change the variable if allowed. if (!data.bNotModifiable) then -- Overwrite the set function if desired. if (data.OnSet) then CHAR["Set"..upperName] = data.OnSet -- Have the set function only set on the server if no networking. elseif (data.bNoNetworking) then CHAR["Set"..upperName] = function(self, value) self.vars[key] = value end -- If the variable is a local one, only send the variable to the local player. elseif (data.isLocal) then CHAR["Set"..upperName] = function(self, value) local oldVar = self.vars[key] self.vars[key] = value net.Start("ixCharacterVarChanged") net.WriteUInt(self:GetID(), 32) net.WriteString(key) net.WriteType(value) net.Send(self.player) hook.Run("CharacterVarChanged", self, key, oldVar, value) end -- Otherwise network the variable to everyone. else CHAR["Set"..upperName] = function(self, value) local oldVar = self.vars[key] self.vars[key] = value net.Start("ixCharacterVarChanged") net.WriteUInt(self:GetID(), 32) net.WriteString(key) net.WriteType(value) net.Broadcast() hook.Run("CharacterVarChanged", self, key, oldVar, value) end end end end -- The get functions are shared. -- Overwrite the get function if desired. if (data.OnGet) then CHAR["Get"..upperName] = data.OnGet -- Otherwise return the character variable or default if it does not exist. else CHAR["Get"..upperName] = function(self, default) local value = self.vars[key] if (value != nil) then return value end if (default == nil) then return ix.char.vars[key] and (istable(ix.char.vars[key].default) and table.Copy(ix.char.vars[key].default) or ix.char.vars[key].default) end return default end end local alias = data.alias if (alias) then if (istable(alias)) then for _, v in ipairs(alias) do local aliasName = v:sub(1, 1):upper()..v:sub(2) CHAR["Get"..aliasName] = CHAR["Get"..upperName] CHAR["Set"..aliasName] = CHAR["Set"..upperName] end elseif (isstring(alias)) then local aliasName = alias:sub(1, 1):upper()..alias:sub(2) CHAR["Get"..aliasName] = CHAR["Get"..upperName] CHAR["Set"..aliasName] = CHAR["Set"..upperName] end end -- Add the variable default to the character object. CHAR.vars[key] = data.default end -- Allows access to the character metatable using ix.meta.character ix.meta.character = CHAR ================================================ FILE: gamemode/core/meta/sh_entity.lua ================================================ --[[-- Physical object in the game world. Entities are physical representations of objects in the game world. Helix extends the functionality of entities to interface between Helix's own classes, and to reduce boilerplate code. See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Entity) for all other methods that the `Player` class has. ]] -- @classmod Entity local meta = FindMetaTable("Entity") local CHAIR_CACHE = {} -- Add chair models to the cache by checking if its vehicle category is a class. for _, v in pairs(list.Get("Vehicles")) do if (v.Category == "Chairs") then CHAIR_CACHE[v.Model] = true end end --- Returns `true` if this entity is a chair. -- @realm shared -- @treturn bool Whether or not this entity is a chair function meta:IsChair() return CHAIR_CACHE[self:GetModel()] end --- Returns `true` if this entity is a door. Internally, this checks to see if the entity's class has `door` in its name. -- @realm shared -- @treturn bool Whether or not the entity is a door function meta:IsDoor() local class = self:GetClass() return (class and class:find("door") != nil) end if (SERVER) then --- Returns `true` if the given entity is a button or door and is locked. -- @realm server -- @treturn bool Whether or not this entity is locked; `false` if this entity cannot be locked at all -- (e.g not a button or door) function meta:IsLocked() if (self:IsVehicle()) then return self:GetInternalVariable( "VehicleLocked" ) end return self:GetInternalVariable( "m_bLocked" ) end --- Returns the neighbouring door entity for double doors. -- @realm shared -- @treturn[1] Entity This door's partner -- @treturn[2] nil If the door does not have a partner function meta:GetDoorPartner() return self.ixPartner end --- Returns the entity that is blocking this door from opening. -- @realm server -- @treturn[1] Entity Entity that is blocking this door -- @treturn[2] nil If this entity is not a door, or there is no blocking entity function meta:GetBlocker() return self:GetInternalVariable( "pBlocker" ) end --- Blasts a door off its hinges. Internally, this hides the door entity, spawns a physics prop with the same model, and -- applies force to the prop. -- @realm server -- @vector velocity Velocity to apply to the door -- @number lifeTime How long to wait in seconds before the door is put back on its hinges -- @bool bIgnorePartner Whether or not to ignore the door's partner in the case of double doors -- @treturn[1] Entity The physics prop created for the door -- @treturn nil If the entity is not a door function meta:BlastDoor(velocity, lifeTime, bIgnorePartner) if (!self:IsDoor()) then return end if (IsValid(self.ixDummy)) then self.ixDummy:Remove() end velocity = velocity or VectorRand()*100 lifeTime = lifeTime or 120 local partner = self:GetDoorPartner() if (IsValid(partner) and !bIgnorePartner) then partner:BlastDoor(velocity, lifeTime, true) end local color = self:GetColor() local dummy = ents.Create("prop_physics") dummy:SetModel(self:GetModel()) dummy:SetPos(self:GetPos()) dummy:SetAngles(self:GetAngles()) dummy:Spawn() dummy:SetColor(color) dummy:SetMaterial(self:GetMaterial()) dummy:SetSkin(self:GetSkin() or 0) dummy:SetRenderMode(RENDERMODE_TRANSALPHA) dummy:CallOnRemove("restoreDoor", function() if (IsValid(self)) then self:SetNotSolid(false) self:SetNoDraw(false) self:DrawShadow(true) self.ignoreUse = false self.ixIsMuted = false for _, v in ents.Iterator() do if (v:GetParent() == self) then v:SetNotSolid(false) v:SetNoDraw(false) if (v.OnDoorRestored) then v:OnDoorRestored(self) end end end end end) dummy:SetOwner(self) dummy:SetCollisionGroup(COLLISION_GROUP_WEAPON) self:Fire("unlock") self:Fire("open") self:SetNotSolid(true) self:SetNoDraw(true) self:DrawShadow(false) self.ignoreUse = true self.ixDummy = dummy self.ixIsMuted = true self:DeleteOnRemove(dummy) for _, v in ipairs(self:GetBodyGroups() or {}) do dummy:SetBodygroup(v.id, self:GetBodygroup(v.id)) end for _, v in ents.Iterator() do if (v:GetParent() == self) then v:SetNotSolid(true) v:SetNoDraw(true) if (v.OnDoorBlasted) then v:OnDoorBlasted(self) end end end dummy:GetPhysicsObject():SetVelocity(velocity) local uniqueID = "doorRestore"..self:EntIndex() local uniqueID2 = "doorOpener"..self:EntIndex() timer.Create(uniqueID2, 1, 0, function() if (IsValid(self) and IsValid(self.ixDummy)) then self:Fire("open") else timer.Remove(uniqueID2) end end) timer.Create(uniqueID, lifeTime, 1, function() if (IsValid(self) and IsValid(dummy)) then uniqueID = "dummyFade"..dummy:EntIndex() local alpha = 255 timer.Create(uniqueID, 0.1, 255, function() if (IsValid(dummy)) then alpha = alpha - 1 dummy:SetColor(ColorAlpha(color, alpha)) if (alpha <= 0) then dummy:Remove() end else timer.Remove(uniqueID) end end) end end) return dummy end else -- Returns the door's slave entity. function meta:GetDoorPartner() local owner = self:GetOwner() or self.ixDoorOwner if (IsValid(owner) and owner:IsDoor()) then return owner end for _, v in ipairs(ents.FindByClass("prop_door_rotating")) do if (v:GetOwner() == self) then self.ixDoorOwner = v return v end end end end ================================================ FILE: gamemode/core/meta/sh_inventory.lua ================================================ --[[-- Holds items within a grid layout. Inventories are an object that contains `Item`s in a grid layout. Every `Character` will have exactly one inventory attached to it, which is the only inventory that is allowed to hold bags - any item that has its own inventory (i.e a suitcase). Inventories can be owned by a character, or it can be individually interacted with as a standalone object. For example, the container plugin attaches inventories to props, allowing for items to be stored outside of any character inventories and remain "in the world". You may be looking for the following common functions: `Add` Which adds an item to the inventory. `GetItems` Which gets all of the items inside the inventory. `GetItemByID` Which gets an item in the inventory by it's item ID. `GetItemAt` Which gets an item in the inventory by it's x and y `GetID` Which gets the inventory's ID. `HasItem` Which checks if the inventory has an item. ]] -- @classmod Inventory local META = ix.meta.inventory or ix.middleclass("ix_inventory") META.slots = META.slots or {} META.w = META.w or 4 META.h = META.h or 4 META.vars = META.vars or {} META.receivers = META.receivers or {} --- Returns a string representation of this inventory -- @realm shared -- @treturn string String representation -- @usage print(ix.item.inventories[1]) -- > "inventory[1]" function META:__tostring() return "inventory["..(self.id or 0).."]" end --- Initializes the inventory with the provided arguments. -- @realm shared -- @internal -- @number id The `Inventory`'s database ID. -- @number width The inventory's width. -- @number height The inventory's height. function META:Initialize(id, width, height) self.id = id self.w = width self.h = height self.slots = {} self.vars = {} self.receivers = {} end --- Returns this inventory's database ID. This is guaranteed to be unique. -- @realm shared -- @treturn number Unique ID of inventory function META:GetID() return self.id or 0 end --- Sets the grid size of this inventory. -- @realm shared -- @internal -- @number width New width of inventory -- @number height New height of inventory function META:SetSize(width, height) self.w = width self.h = height end --- Returns the grid size of this inventory. -- @realm shared -- @treturn number Width of inventory -- @treturn number Height of inventory function META:GetSize() return self.w, self.h end -- this is pretty good to debug/develop function to use. function META:Print(printPos) for k, v in pairs(self:GetItems()) do local str = k .. ": " .. v.name if (printPos) then str = str .. " (" .. v.gridX .. ", " .. v.gridY .. ")" end print(str) end end --- Searches the inventory to find any stacked items. -- A common problem with developing, is that items will sometimes error out, or get corrupt. -- Sometimes, the server knows things you don't while developing live -- This function can be helpful for getting rid of those pesky errors. -- @realm shared function META:FindError() for k, _ in self:Iter() do if (k.width == 1 and k.height == 1) then continue end print("Finding error: " .. k.name) print("Item Position: " .. k.gridX, k.gridY) for x = k.gridX, k.gridX + k.width - 1 do for y = k.gridY, k.gridY + k.height - 1 do local item = self.slots[x][y] if (item and item.id != k.id) then print("Error Found: ".. item.name) end end end end end --- Prints out the id, width, height, slots and each item in each slot of an `Inventory`, used for debugging. -- @realm shared function META:PrintAll() print("------------------------") print("INVID", self:GetID()) print("INVSIZE", self:GetSize()) if (self.slots) then for x = 1, self.w do for y = 1, self.h do local item = self.slots[x] and self.slots[x][y] if (item and item.id) then print(item.name .. "(" .. item.id .. ")", x, y) end end end end print("INVVARS") PrintTable(self.vars or {}) print("------------------------") end --- Returns the player that owns this inventory. -- @realm shared -- @treturn[1] Player Owning player -- @treturn[2] nil If no connected player owns this inventory function META:GetOwner() for _, v in player.Iterator() do if (v:GetCharacter() and v:GetCharacter().id == self.owner) then return v end end end --- Sets the player that owns this inventory. -- @realm shared -- @player owner The player to take control over the inventory. -- @bool fullUpdate Whether or not to update the inventory immediately to the new owner. function META:SetOwner(owner, fullUpdate) if (type(owner) == "Player" and owner:GetNetVar("char")) then owner = owner:GetNetVar("char") elseif (!isnumber(owner)) then return end if (SERVER) then if (fullUpdate) then for _, v in player.Iterator() do if (v:GetNetVar("char") == owner) then self:Sync(v, true) break end end end local query = mysql:Update("ix_inventories") query:Update("character_id", owner) query:Where("inventory_id", self:GetID()) query:Execute() end self.owner = owner end --- Checks whether a player has access to an inventory -- @realm shared -- @internal -- @player client Player to check access for -- @treturn bool Whether or not the player has access to the inventory function META:OnCheckAccess(client) local bAccess = false for _, v in ipairs(self:GetReceivers()) do if (v == client) then bAccess = true break end end return bAccess end --- Checks whether or not an `Item` can fit into the `Inventory` starting from `x` and `y`. -- Internally used by FindEmptySlot, in most cases you are better off using that. -- This function will search if all of the slots within `x + width` and `y + width` are empty, -- ignoring any space the `Item` itself already occupies. -- @realm shared -- @internal -- @number x The beginning x coordinate to search for. -- @number y The beginning y coordiate to search for. -- @number w The `Item`'s width. -- @number h The `Item`'s height. -- @item[opt=nil] item2 An `Item`, if any, to ignore when searching. function META:CanItemFit(x, y, w, h, item2) local canFit = true for x2 = 0, w - 1 do for y2 = 0, h - 1 do local item = (self.slots[x + x2] or {})[y + y2] if ((x + x2) > self.w or item) then if (item2) then if (item and item.id == item2.id) then continue end end canFit = false break end end if (!canFit) then break end end return canFit end --- Returns the amount of slots currently filled in the Inventory. -- @realm shared -- @treturn number The amount of slots currently filled. function META:GetFilledSlotCount() local count = 0 for x = 1, self.w do for y = 1, self.h do if ((self.slots[x] or {})[y]) then count = count + 1 end end end return count end --- Finds an empty slot of a specified width and height. -- In most cases, to check if an `Item` can actually fit in the `Inventory`, -- as if it can't, it will just return `nil`. -- -- FindEmptySlot will loop through all the slots for you, as opposed to `CanItemFit` -- which you specify an `x` and `y` for. -- this will call CanItemFit anyway. -- If you need to check if an item will fit *exactly* at a position, you want CanItemFit instead. -- @realm shared -- @number w The width of the `Item` you are trying to fit. -- @number h The height of the `Item` you are trying to fit. -- @bool onlyMain Whether or not to search any bags connected to this `Inventory` -- @treturn[1] number x The `x` coordinate that the `Item` can fit into. -- @treturn[1] number y The `y` coordinate that the `Item` can fit into. -- @treturn[2] number x The `x` coordinate that the `Item` can fit into. -- @treturn[2] number y The `y` coordinate that the `Item` can fit into. -- @treturn[2] Inventory bagInv If the item was in a bag, it will return the inventory it was in. -- @see CanItemFit function META:FindEmptySlot(w, h, onlyMain) w = w or 1 h = h or 1 if (w > self.w or h > self.h) then return end for y = 1, self.h - (h - 1) do for x = 1, self.w - (w - 1) do if (self:CanItemFit(x, y, w, h)) then return x, y end end end if (onlyMain != true) then local bags = self:GetBags() if (#bags > 0) then for _, invID in ipairs(bags) do local bagInv = ix.item.inventories[invID] if (bagInv) then local x, y = bagInv:FindEmptySlot(w, h) if (x and y) then return x, y, bagInv end end end end end end --- Returns the item that currently exists within `x` and `y` in the `Inventory`. -- Items that have a width or height greater than 0 occupy more than 1 x and y. -- @realm shared -- @number x The `x` coordindate to search in. -- @number y The `y` coordinate to search in. -- @treturn number x The `x` coordinate that the `Item` is located at. -- @treturn number y The `y` coordinate that the `Item` is located at. function META:GetItemAt(x, y) if (self.slots and self.slots[x]) then return self.slots[x][y] end end --- Removes an item from the inventory. -- @realm shared -- @number id The item instance ID to remove -- @bool[opt=false] bNoReplication Whether or not the item's removal should not be replicated -- @bool[opt=false] bNoDelete Whether or not the item should not be fully deleted -- @bool[opt=false] bTransferring Whether or not the item is being transferred to another inventory -- @treturn number The X position that the item was removed from -- @treturn number The Y position that the item was removed from function META:Remove(id, bNoReplication, bNoDelete, bTransferring) local x2, y2 for x = 1, self.w do if (self.slots[x]) then for y = 1, self.h do local item = self.slots[x][y] if (item and item.id == id) then self.slots[x][y] = nil x2 = x2 or x y2 = y2 or y end end end end if (SERVER and !bNoReplication) then local receivers = self:GetReceivers() if (istable(receivers)) then net.Start("ixInventoryRemove") net.WriteUInt(id, 32) net.WriteUInt(self:GetID(), 32) net.Send(receivers) end -- we aren't removing the item - we're transferring it to another inventory if (!bTransferring) then hook.Run("InventoryItemRemoved", self, ix.item.instances[id]) end if (!bNoDelete) then local item = ix.item.instances[id] if (item and item.OnRemoved) then item:OnRemoved() end local query = mysql:Delete("ix_items") query:Where("item_id", id) query:Execute() ix.item.instances[id] = nil end end return x2, y2 end --- Adds a player as a receiver on this `Inventory` -- Receivers are players who will be networked the items inside the inventory. -- -- Calling this will *not* automatically sync it's current contents to the client. -- All future contents will be synced, but not anything that was not synced before this is called. -- -- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error. -- @realm shared -- @player client The player to add as a receiver. function META:AddReceiver(client) self.receivers[client] = true end --- The opposite of `AddReceiver`. -- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error. -- @realm shared -- @player client The player to remove from the receiver list. function META:RemoveReceiver(client) self.receivers[client] = nil end --- Get all of the receivers this `Inventory` has. -- Receivers are players who will be networked the items inside the inventory. -- -- This function will automatically sort out invalid players for you. -- @realm shared -- @treturn table result The players who are on the server and allowed to see this table. function META:GetReceivers() local result = {} if (self.receivers) then for k, _ in pairs(self.receivers) do if (IsValid(k) and k:IsPlayer()) then result[#result + 1] = k end end end return result end --- Returns a count of a *specific* `Item`s in the `Inventory` -- @realm shared -- @string uniqueID The Unique ID of the item. -- @bool onlyMain Whether or not to exclude bags that are present from the search. -- @treturn number The amount of `Item`s this inventory has. -- @usage local curHighest, winner = 0, false -- for client, character in ix.util.GetCharacters() do -- local itemCount = character:GetInventory():GetItemCount('water', false) -- if itemCount > curHighest then -- curHighest = itemCount -- winner = character -- end -- end -- -- Finds the thirstiest character on the server and returns their Character ID or false if no character has water. function META:GetItemCount(uniqueID, onlyMain) local i = 0 for _, v in pairs(self:GetItems(onlyMain)) do if (v.uniqueID == uniqueID) then i = i + 1 end end return i end --- Returns a table of all `Item`s in the `Inventory` by their Unique ID. -- Not to be confused with `GetItemsByID` or `GetItemByID` which take in an Item Instance's ID instead. -- @realm shared -- @string uniqueID The Unique ID of the item. -- @bool onlyMain Whether or not to exclude bags that are present from the search. -- @treturn number The table of specified `Item`s this inventory has. function META:GetItemsByUniqueID(uniqueID, onlyMain) local items = {} for _, v in pairs(self:GetItems(onlyMain)) do if (v.uniqueID == uniqueID) then items[#items + 1] = v end end return items end --- Returns a table of `Item`s by their base. -- @realm shared -- @string baseID The base to search for. -- @bool bOnlyMain Whether or not to exclude bags that are present from the search. function META:GetItemsByBase(baseID, bOnlyMain) local items = {} for _, v in pairs(self:GetItems(bOnlyMain)) do if (v.base == baseID) then items[#items + 1] = v end end return items end --- Get an item by it's specific Database ID. -- @realm shared -- @number id The ID to search for. -- @bool onlyMain Whether or not to exclude bags that are present from the search. -- @treturn item The item if it exists. function META:GetItemByID(id, onlyMain) for _, v in pairs(self:GetItems(onlyMain)) do if (v.id == id) then return v end end end --- Get a table of `Item`s by their specific Database ID. -- It's important to note that while in 99% of cases, -- items will have a unique Database ID, developers or random GMod weirdness could -- cause a second item with the same ID to appear, even though, `ix.item.instances` will only store one of those. -- The inventory only stores a reference to the `ix.item.instance` ID, not the memory reference itself. -- @realm shared -- @number id The ID to search for. -- @bool onlyMain Whether or not to exclude bags that are present from the search. -- @treturn item The item if it exists. function META:GetItemsByID(id, onlyMain) local items = {} for _, v in pairs(self:GetItems(onlyMain)) do if (v.id == id) then items[#items + 1] = v end end return items end -- This function may pretty heavy. --- Returns a table of all the items that an `Inventory` has. -- @realm shared -- @bool onlyMain Whether or not to exclude bags from this search. -- @treturn table The items this `Inventory` has. function META:GetItems(onlyMain) local items = {} for _, v in pairs(self.slots) do for _, v2 in pairs(v) do if (istable(v2) and !items[v2.id]) then items[v2.id] = v2 v2.data = v2.data or {} local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id) if (isBag and isBag != self:GetID() and onlyMain != true) then local bagInv = ix.item.inventories[isBag] if (bagInv) then local bagItems = bagInv:GetItems() table.Merge(items, bagItems) end end end end end return items end --- Returns an iterator that returns all contained items, a better way to iterate items than `pairs(inventory:GetItems())` -- @realm shared -- @treturn function iterator function META:Iter() local x, y, item = 1, 1 return function() item = nil repeat if (x > self.w) then x, y = 1, y + 1 if (y > self.h) then return nil end end item = self.slots[x] and self.slots[x][y] x = x + 1 until item and not isbool(item) -- skip reserved slots in which items have yet to be fully instantiated if (item) then return item, x, y end end end -- This function may pretty heavy. --- Returns a table of all the items that an `Inventory` has. -- @realm shared -- @bool onlyMain Whether or not to exclude bags from this search. -- @treturn table The items this `Inventory` has. function META:GetBags() local invs = {} for _, v in pairs(self.slots) do for _, v2 in pairs(v) do if (istable(v2) and v2.data) then local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id) if (!table.HasValue(invs, isBag)) then if (isBag and isBag != self:GetID()) then invs[#invs + 1] = isBag end end end end end return invs end --- Returns the item with the given unique ID (e.g `"handheld_radio"`) if it exists in this inventory. -- This method checks both -- this inventory, and any bags that this inventory has inside of it. -- @realm shared -- @string targetID Unique ID of the item to look for -- @tab[opt] data Item data to check for -- @treturn[1] Item Item that belongs to this inventory with the given criteria -- @treturn[2] bool `false` if the item does not exist -- @see HasItems -- @see HasItemOfBase -- @usage local item = inventory:HasItem("handheld_radio") -- -- if (item) then -- -- do something with the item table -- end function META:HasItem(targetID, data) for k, _ in self:Iter() do if (k.uniqueID == targetID) then if (data) then local itemData = k.data local bFound = true for dataKey, dataVal in pairs(data) do if (itemData[dataKey] != dataVal) then bFound = false break end end if (!bFound) then continue end end return k end end return false end --- Checks whether or not the `Inventory` has a table of items. -- This function takes a table with **no** keys and runs in order of first item > last item, --this is due to the usage of the `#` operator in the function. -- -- @realm shared -- @tab targetIDs A table of `Item` Unique ID's. -- @treturn[1] bool true Whether or not the `Inventory` has all of the items. -- @treturn[1] table targetIDs Your provided targetIDs table, but it will be empty. -- @treturn[2] bool false -- @treturn[2] table targetIDs Table consisting of the items the `Inventory` did **not** have. -- @usage local itemFilter = {'water', 'water_sparkling'} -- if not Entity(1):GetCharacter():GetInventory():HasItems(itemFilter) then return end -- -- Filters out if this player has both a water, and a sparkling water. function META:HasItems(targetIDs) local count = #targetIDs -- assuming array targetIDs = table.Copy(targetIDs) for item, _ in self:Iter() do for k, targetID in ipairs(targetIDs) do if (item.uniqueID == targetID) then table.remove(targetIDs, k) count = count - 1 break end end end return count <= 0, targetIDs end --- Whether or not an `Inventory` has an item of a base, optionally with specified data. -- This function has an optional `data` argument, which will take a `table`. -- it will match if the data of the item is correct or not. -- -- Items which are a base will automatically have base_ prefixed to their Unique ID, if you are having -- trouble finding your base, that is probably why. -- @realm shared -- @string baseID The Item Base's Unique ID. -- @tab[opt] data The Item's data to compare against. -- @treturn[1] item The first `Item` of `baseID` that is found and there is no `data` argument or `data` was matched. -- @treturn[2] false If no `Item`s of `baseID` is found or the `data` argument, if specified didn't match. -- @usage local bHasWeaponEquipped = Entity(1):GetCharacter():GetInventory():HasItemOfBase('base_weapons', {['equip'] = true}) -- if bHasWeaponEquipped then -- Entity(1):Notify('One gun is fun, two guns is Woo-tastic.') -- end -- -- Notifies the player that they should get some more guns. function META:HasItemOfBase(baseID, data) for k, _ in self:Iter() do if (k.base == baseID) then if (data) then local itemData = k.data local bFound = true for dataKey, dataVal in pairs(data) do if (itemData[dataKey] != dataVal) then bFound = false break end end if (!bFound) then continue end end return k end end return false end if (SERVER) then --- Sends a specific slot to a character. -- This will *not* send all of the slots of the `Item` to the character, items can occupy multiple slots. -- -- This will call `OnSendData` on the Item using all of the `Inventory`'s receivers. -- -- This function should *not* be used to sync an entire inventory, if you need to do that, use `AddReceiver` and `Sync`. -- @realm server -- @internal -- @number x The Inventory x position to send. -- @number y The Inventory y position to send. -- @item[opt] item The item to send, if any. -- @see AddReceiver -- @see Sync function META:SendSlot(x, y, item) local receivers = self:GetReceivers() local sendData = item and item.data and !table.IsEmpty(item.data) and item.data or {} net.Start("ixInventorySet") net.WriteUInt(self:GetID(), 32) net.WriteUInt(x, 6) net.WriteUInt(y, 6) net.WriteString(item and item.uniqueID or "") net.WriteUInt(item and item.id or 0, 32) net.WriteUInt(self.owner or 0, 32) net.WriteTable(sendData) net.Send(receivers) if (item) then for _, v in pairs(receivers) do item:Call("OnSendData", v) end end end --- Sets whether or not an `Inventory` should save. -- This will prevent an `Inventory` from updating in the Database, if the inventory is already saved, -- it will not be deleted when unloaded. -- @realm server -- @bool bNoSave Whether or not the Inventory should save. function META:SetShouldSave(bNoSave) self.noSave = bNoSave end --- Gets whether or not an `Inventory` should save. -- Inventories that are marked to not save will not update in the Database, if they inventory is already saved, -- it will not be deleted when unloaded. -- @realm server -- @treturn[1] bool Returns the field `noSave`. -- @treturn[2] bool Returns true if the field `noSave` is not registered to this inventory. function META:GetShouldSave() return self.noSave or true end --- Add an item to the inventory. -- @realm server -- @param uniqueID The item unique ID (e.g `"handheld_radio"`) or instance ID (e.g `1024`) to add to the inventory -- @number[opt=1] quantity The quantity of the item to add -- @tab data Item data to add to the item -- @number[opt=nil] x The X position for the item -- @number[opt=nil] y The Y position for the item -- @bool[opt=false] noReplication Whether or not the item's addition should not be replicated -- @treturn[1] bool Whether the add was successful or not -- @treturn[1] string The error, if applicable -- @treturn[2] number The X position that the item was added to -- @treturn[2] number The Y position that the item was added to -- @treturn[2] number The inventory ID that the item was added to function META:Add(uniqueID, quantity, data, x, y, noReplication) quantity = quantity or 1 if (quantity < 1) then return false, "noOwner" end if (!isnumber(uniqueID) and quantity > 1) then for _ = 1, quantity do local bSuccess, error = self:Add(uniqueID, 1, data) if (!bSuccess) then return false, error end end return true end local client = self.GetOwner and self:GetOwner() or nil local item = isnumber(uniqueID) and ix.item.instances[uniqueID] or ix.item.list[uniqueID] local targetInv = self local bagInv if (!item) then return false, "invalidItem" end if (isnumber(uniqueID)) then local oldInvID = item.invID if (!x and !y) then x, y, bagInv = self:FindEmptySlot(item.width, item.height) end if (bagInv) then targetInv = bagInv end -- we need to check for owner since the item instance already exists if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter() and item:GetPlayerID() == client:SteamID64() and item:GetCharacterID() != client:GetCharacter():GetID()) then return false, "itemOwned" end if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv) == false) then return false, "notAllowed" end if (x and y) then targetInv.slots[x] = targetInv.slots[x] or {} targetInv.slots[x][y] = true item.gridX = x item.gridY = y item.invID = targetInv:GetID() for x2 = 0, item.width - 1 do local index = x + x2 for y2 = 0, item.height - 1 do targetInv.slots[index] = targetInv.slots[index] or {} targetInv.slots[index][y + y2] = item end end if (!noReplication) then targetInv:SendSlot(x, y, item) end if (!self.noSave) then local query = mysql:Update("ix_items") query:Update("inventory_id", targetInv:GetID()) query:Update("x", x) query:Update("y", y) query:Where("item_id", item.id) query:Execute() end hook.Run("InventoryItemAdded", ix.item.inventories[oldInvID], targetInv, item) return x, y, targetInv:GetID() else return false, "noFit" end else if (!x and !y) then x, y, bagInv = self:FindEmptySlot(item.width, item.height) end if (bagInv) then targetInv = bagInv end if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv) == false) then return false, "notAllowed" end if (x and y) then for x2 = 0, item.width - 1 do local index = x + x2 for y2 = 0, item.height - 1 do targetInv.slots[index] = targetInv.slots[index] or {} targetInv.slots[index][y + y2] = true end end local characterID local playerID if (self.owner) then local character = ix.char.loaded[self.owner] if (character) then characterID = character.id playerID = character.steamID end end ix.item.Instance(targetInv:GetID(), uniqueID, data, x, y, function(newItem) newItem.gridX = x newItem.gridY = y for x2 = 0, newItem.width - 1 do local index = x + x2 for y2 = 0, newItem.height - 1 do targetInv.slots[index] = targetInv.slots[index] or {} targetInv.slots[index][y + y2] = newItem end end if (!noReplication) then targetInv:SendSlot(x, y, newItem) end hook.Run("InventoryItemAdded", nil, targetInv, newItem) end, characterID, playerID) return x, y, targetInv:GetID() else return false, "noFit" end end end --- Syncs the `Inventory` to the receiver. -- This will call Item.OnSendData on every item in the `Inventory`. -- @realm server -- @player receiver The player to function META:Sync(receiver) local slots = {} for x, items in pairs(self.slots) do for y, item in pairs(items) do if (istable(item) and item.gridX == x and item.gridY == y) then slots[#slots + 1] = {x, y, item.uniqueID, item.id, item.data} end end end net.Start("ixInventorySync") net.WriteTable(slots) net.WriteUInt(self:GetID(), 32) net.WriteUInt(self.w, 6) net.WriteUInt(self.h, 6) net.WriteType(self.owner) net.WriteTable(self.vars or {}) net.Send(receiver) for k, _ in self:Iter() do k:Call("OnSendData", receiver) end end end ix.meta.inventory = META ================================================ FILE: gamemode/core/meta/sh_item.lua ================================================ --[[-- Interactable entities that can be held in inventories. Items are objects that are contained inside of an `Inventory`, or as standalone entities if they are dropped in the world. They usually have functionality that provides more gameplay aspects to the schema. For example, the zipties in the HL2 RP schema allow a player to tie up and search a player. For an item to have an actual presence, they need to be instanced (usually with `ix.item.Instance`). Items describe the properties, while instances are a clone of these properties that can have their own unique data (e.g an ID card will have the same name but different numerical IDs). You can think of items as the class, while instances are objects of the `Item` class. ## Creating item classes (`ItemStructure`) Item classes are defined in their own file inside of your schema or plugin's `items/` folder. In these item class files you specify how instances of the item behave. This includes default values for basic things like the item's name and description, to more advanced things by overriding extra methods from an item base. See `ItemStructure` for information on how to define a basic item class. Item classes in this folder are automatically loaded by Helix when the server starts up. ## Item bases If many items share the same functionality (i.e a can of soda and a bottle of water can both be consumed), then you might want to consider using an item base to reduce the amount of duplication for these items. Item bases are defined the same way as regular item classes, but they are placed in the `items/base/` folder in your schema or plugin. For example, a `consumables` base would be in `items/base/sh_consumables.lua`. Any items that you want to use this base must be placed in a subfolder that has the name of the base you want that item to use. For example, for a bottled water item to use the consumable base, it must be placed in `items/consumables/sh_bottled_water.lua`. This also means that you cannot place items into subfolders as you wish, since the framework will try to use an item base that doesn't exist. The default item bases that come with Helix are: - `ammo` - provides ammo to any items with the `weapons` base - `bags` - holds an inventory that other items can be stored inside of - `outfit` - changes the appearance of the player that wears it - `pacoutfit` - changes the appearance of the player that wears it using PAC3 - `weapons` - makes any SWEP into an item that can be equipped These item bases usually come with extra values and methods that you can define/override in order to change their functionality. You should take a look at the source code for these bases to see their capabilities. ## Item functions (`ItemFunctionStructure`) Requiring players to interact with items in order for them to do something is quite common. As such, there is already a built-in mechanism to allow players to right-click items and show a list of available options. Item functions are defined in your item class file in the `ITEM.functions` table. See `ItemFunctionStructure` on how to define them. Helix comes with `drop`, `take`, and `combine` item functions by default that allows items to be dropped from a player's inventory, picked up from the world, and combining items together. These can be overridden by defining an item function in your item class file with the same name. See the `bags` base for example usage of the `combine` item function. ## Item icons (`ItemIconStructure`) Icons for items sometimes don't line up quite right, in which case you can modify an item's `iconCam` value and line up the rendered model as needed. See `ItemIconStructure` for more details. ]] -- @classmod Item --[[-- All item functions live inside of an item's `functions` table. An item function entry includes a few methods and fields you can use to customize the functionality and appearance of the item function. An example item function is below: -- this item function's unique ID is "MyFunction" ITEM.functions.MyFunction = { name = "myFunctionPhrase", -- uses the "myFunctionPhrase" language phrase when displaying in the UI tip = "myFunctionDescription", -- uses the "myFunctionDescription" language phrase when displaying in the UI icon = "icon16/add.png", -- path to the icon material OnRun = function(item) local client = item.player local entity = item.entity -- only set if this is function is being ran while the item is in the world if (IsValid(client)) then client:ChatPrint("This is a test.") if (IsValid(entity)) then client:ChatPrint(entity:GetName()) end end -- do not remove this item from the owning player's inventory return false end, OnCanRun = function(item) -- only allow admins to run this item function local client = item.player return IsValid(client) and client:IsAdmin() end } ]] -- @table ItemFunctionStructure -- @realm shared -- @field[type=string,opt] name Language phrase to use when displaying this item function's name in the UI. If not specified, -- then it will use the unique ID of the item function -- @field[type=string,opt] tip Language phrase to use when displaying this item function's detailed description in the UI -- @field[type=string,opt] icon Path to the material to use when displaying this item function's icon -- @field[type=function] OnRun Function to call when the item function is ran. This function is **ONLY** ran on the server. -- -- The only argument passed into this function is the instance of the item being called. The instance will have its `player` -- field set if the item function is being ran by a player (which it should be most of the time). It will also have its `entity` -- field set if the item function is being ran while the item is in the world, and not in a player's inventory. -- -- The item will be removed after the item function is ran. If you want to prevent this behaviour, then you can return `false` -- in this function. See the example above. -- @field[type=function] OnCanRun Function to call when checking whether or not this item function can be ran. This function is -- ran **BOTH** on the client and server. -- -- The arguments are the same as `OnCanRun`, and the `player` and `entity` fields will be set on the item instance accordingly. -- Returning `true` will allow the item function to be ran. Returning `false` will prevent it from running and additionally -- hide it from the UI. See the example above. -- @field[type=function,opt] OnClick This function is called when the player clicks on this item function's entry in the UI. -- This function is ran **ONLY** on the client, and is only ran if `OnCanRun` succeeds. -- -- The same arguments from `OnCanRun` and `OnRun` apply to this function. --[[-- Changing the way an item's icon is rendered is done by modifying the location and angle of the model, as well as the FOV of the camera. You can tweak the values in code, or use the `ix_dev_icon` console command to visually position the model and camera. An example entry for an item's icon is below: ITEM.iconCam = { pos = Vector(0, 0, 60), ang = Angle(90, 0, 0), fov = 45 } Note that this will probably not work for your item's specific model, since every model has a different size, origin, etc. All item icons need to be tweaked individually. ]] -- @table ItemIconStructure -- @realm client -- @field[type=vector] pos Location of the model relative to the camera. +X is forward, +Z is up -- @field[type=angle] ang Angle of the model -- @field[type=number] fov FOV of the camera --[[-- When creating an item class, the file will have a global table `ITEM` set that you use to define the item's values/methods. An example item class is below: `items/sh_brick.lua` ITEM.name = "Brick" ITEM.description = "A brick. Pretty self-explanatory. You can eat it but you'll probably lose some teeth." ITEM.model = Model("models/props_debris/concrete_cynderblock001.mdl") ITEM.width = 1 ITEM.height = 1 ITEM.price = 25 Note that the below list only includes the default fields available for *all* items, and not special ones defined in custom item bases. ]] -- @table ItemStructure -- @realm shared -- @field[type=string] name Display name of the item -- @field[type=string] description Detailed description of the item -- @field[type=string] model Model to use for the item's icon and when it's dropped in the world -- @field[type=number,opt=1] width Width of the item in grid cells -- @field[type=number,opt=1] height Height of the item in grid cells -- @field[type=number,opt=0] price How much money it costs to purchase this item in the business menu -- @field[type=string,opt] category Name of the category this item belongs to - mainly used for the business menu -- @field[type=boolean,opt=false] noBusiness Whether or not to disallow purchasing this item in the business menu -- @field[type=table,opt] factions List of factions allowed to purchase this item in the business menu -- @field[type=table,opt] classes List of character classes allowed to purchase this item in the business menu. Classes are -- checked after factions, so the character must also be in an allowed faction -- @field[type=string,opt] flag List of flags (as a string - e.g `"a"` or `"abc"`) allowed to purchase this item in the -- business menu. Flags are checked last, so the character must also be in an allowed faction and class -- @field[type=ItemIconStructure,opt] iconCam How to render this item's icon -- @field[type=table,opt] functions List of all item functions that this item has. See `ItemFunctionStructure` on how to define -- new item functions local ITEM = ix.meta.item or {} ITEM.__index = ITEM ITEM.name = "Undefined" ITEM.description = ITEM.description or "An item that is undefined." ITEM.id = ITEM.id or 0 ITEM.uniqueID = "undefined" --- Returns a string representation of this item. -- @realm shared -- @treturn string String representation -- @usage print(ix.item.instances[1]) -- > "item[1]" function ITEM:__tostring() return "item["..self.uniqueID.."]["..self.id.."]" end --- Returns true if this item is equal to another item. Internally, this checks item IDs. -- @realm shared -- @item other Item to compare to -- @treturn bool Whether or not this item is equal to the given item -- @usage print(ix.item.instances[1] == ix.item.instances[2]) -- > false function ITEM:__eq(other) return self:GetID() == other:GetID() end --- Returns this item's database ID. This is guaranteed to be unique. -- @realm shared -- @treturn number Unique ID of item function ITEM:GetID() return self.id end --- Returns the name of the item. -- @realm shared -- @treturn string The name of the item function ITEM:GetName() return (CLIENT and L(self.name) or self.name) end --- Returns the description of the item. -- @realm shared -- @treturn string The description of the item function ITEM:GetDescription() if (!self.description) then return "ERROR" end return L(self.description or "noDesc") end --- Returns the model of the item. -- @realm shared -- @treturn string The model of the item function ITEM:GetModel() return self.model end --- Returns the skin of the item. -- @realm shared -- @treturn number The skin of the item function ITEM:GetSkin() return self.skin or 0 end function ITEM:GetMaterial() return nil end --- Returns the ID of the owning character, if one exists. -- @realm shared -- @treturn number The owning character's ID function ITEM:GetCharacterID() return self.characterID end --- Returns the SteamID64 of the owning player, if one exists. -- @realm shared -- @treturn number The owning player's SteamID64 function ITEM:GetPlayerID() return self.playerID end --- A utility function which prints the item's details. -- @realm shared -- @bool[opt=false] detail Whether additional detail should be printed or not(Owner, X position, Y position) function ITEM:Print(detail) if (detail == true) then print(Format("%s[%s]: >> [%s](%s,%s)", self.uniqueID, self.id, self.owner, self.gridX, self.gridY)) else print(Format("%s[%s]", self.uniqueID, self.id)) end end --- A utility function printing the item's stored data. -- @realm shared function ITEM:PrintData() self:Print(true) print("ITEM DATA:") for k, v in pairs(self.data) do print(Format("[%s] = %s", k, v)) end end --- Calls one of the item's methods. -- @realm shared -- @string method The method to be called -- @player client The client to pass when calling the method, if applicable -- @entity entity The eneity to pass when calling the method, if applicable -- @param ... Arguments to pass to the method -- @return The values returned by the method function ITEM:Call(method, client, entity, ...) local oldPlayer, oldEntity = self.player, self.entity self.player = client or self.player self.entity = entity or self.entity if (isfunction(self[method])) then local results = {self[method](self, ...)} self.player = nil self.entity = nil return unpack(results) end self.player = oldPlayer self.entity = oldEntity end --- Returns the player that owns this item. -- @realm shared -- @treturn player Player owning this item function ITEM:GetOwner() local inventory = ix.item.inventories[self.invID] if (inventory) then return inventory.GetOwner and inventory:GetOwner() end local id = self:GetID() for _, v in player.Iterator() do local character = v:GetCharacter() if (character and character:GetInventory() and character:GetInventory():GetItemByID(id)) then return v end end end --- Sets a key within the item's data. -- @realm shared -- @string key The key to store the value within -- @param[opt=nil] value The value to store within the key -- @tab[opt=nil] receivers The players to replicate the data on -- @bool[opt=false] noSave Whether to disable saving the data on the database or not -- @bool[opt=false] noCheckEntity Whether to disable setting the data on the entity, if applicable function ITEM:SetData(key, value, receivers, noSave, noCheckEntity) self.data = self.data or {} self.data[key] = value if (SERVER) then if (!noCheckEntity) then local ent = self:GetEntity() if (IsValid(ent)) then local data = ent:GetNetVar("data", {}) data[key] = value ent:SetNetVar("data", data) end end end local inventory = ix.item.inventories[self.invID] if (receivers != false) then local targets = receivers or (inventory and inventory.GetReceivers and inventory:GetReceivers()) or self:GetOwner() if (targets) then net.Start("ixInventoryData") net.WriteUInt(self:GetID(), 32) net.WriteString(key) net.WriteType(value) net.Send(targets) end end if (!noSave and ix.db) then local query = mysql:Update("ix_items") query:Update("data", util.TableToJSON(self.data)) query:Where("item_id", self:GetID()) query:Execute() end end --- Returns the value stored on a key within the item's data. -- @realm shared -- @string key The key in which the value is stored -- @param[opt=nil] default The value to return in case there is no value stored in the key -- @return The value stored within the key function ITEM:GetData(key, default) self.data = self.data or {} if (self.data) then if (key == true) then return self.data end local value = self.data[key] if (value != nil) then return value elseif (IsValid(self.entity)) then local data = self.entity:GetNetVar("data", {}) value = data[key] if (value != nil) then return value end end else self.data = {} end if (default != nil) then return default end return end --- Changes the function called on specific events for the item. -- @realm shared -- @string name The name of the hook -- @func func The function to call once the event occurs function ITEM:Hook(name, func) if (name) then self.hooks[name] = func end end --- Changes the function called after hooks for specific events for the item. -- @realm shared -- @string name The name of the hook -- @func func The function to call after the original hook was called function ITEM:PostHook(name, func) if (name) then self.postHooks[name] = func end end --- Removes the item. -- @realm shared -- @bool bNoReplication Whether or not the item's removal should not be replicated. -- @bool bNoDelete Whether or not the item should not be fully deleted -- @treturn bool Whether the item was successfully deleted or not function ITEM:Remove(bNoReplication, bNoDelete) local inv = ix.item.inventories[self.invID] local bFailed = false if (self.invID > 0 and inv) then for x = self.gridX, self.gridX + (self.width - 1) do if (inv.slots[x]) then for y = self.gridY, self.gridY + (self.height - 1) do local item = inv.slots[x][y] if (item and item.id == self.id) then inv.slots[x][y] = nil else bFailed = true end end else bFailed = true end end if (bFailed) then local items = {} for _, v in pairs(ix.item.instances) do if (v.invID == self.invID and v.id != self.id) then items[#items + 1] = v end end inv.slots = {} for _, v in ipairs(items) do for x = v.gridX, v.gridX + (v.width - 1) do for y = v.gridY, v.gridY + (v.height - 1) do inv.slots[x] = inv.slots[x] or {} inv.slots[x][y] = v end end end end else -- @todo definition probably isn't needed inv = ix.item.inventories[self.invID] if (inv) then ix.item.inventories[self.invID][self.id] = nil end end if (SERVER and !bNoReplication) then local entity = self:GetEntity() if (IsValid(entity)) then entity:Remove() end local receivers = inv.GetReceivers and inv:GetReceivers() if (self.invID != 0 and istable(receivers)) then if (bFailed) then inv:Sync(receivers) else net.Start("ixInventoryRemove") net.WriteUInt(self.id, 32) net.WriteUInt(self.invID, 32) net.Send(receivers) end end if (!bNoDelete) then local item = ix.item.instances[self.id] if (inv and inv.owner) then hook.Run("InventoryItemRemoved", inv, item) end if (item and item.OnRemoved) then item:OnRemoved() end local query = mysql:Delete("ix_items") query:Where("item_id", self.id) query:Execute() ix.item.instances[self.id] = nil end end return true end if (SERVER) then --- Returns the item's entity. -- @realm server -- @treturn entity The entity of the item function ITEM:GetEntity() local id = self:GetID() for _, v in ipairs(ents.FindByClass("ix_item")) do if (v.ixItemID == id) then return v end end end --- Spawn an item entity based off the item table. -- @realm server -- @param[type=vector] position The position in which the item's entity will be spawned -- @param[type=angle] angles The angles at which the item's entity will spawn -- @treturn entity The spawned entity function ITEM:Spawn(position, angles) -- Check if the item has been created before. if (ix.item.instances[self.id]) then local client -- Spawn the actual item entity. local entity = ents.Create("ix_item") entity:Spawn() entity:SetAngles(angles or Angle(0, 0, 0)) entity:SetItem(self.id) -- If the first argument is a player, then we will find a position to drop -- the item based off their aim. if (type(position) == "Player") then client = position position = position:GetItemDropPos(entity) end entity:SetPos(position) if (IsValid(client)) then entity.ixSteamID = client:SteamID() entity.ixCharID = client:GetCharacter():GetID() entity:SetNetVar("owner", entity.ixCharID) end hook.Run("OnItemSpawned", entity) return entity end end --- Transfers an item to a specific inventory. -- @realm server -- @number invID The inventory to transfer the item to -- @number x The X position to which the item should be transferred on the new inventory -- @number y The Y position to which the item should be transferred on the new inventory -- @player client The player to which the item is being transferred -- @bool noReplication Whether there should be no replication of the transferral -- @bool isLogical Whether or not an entity should spawn if the item is transferred to the world -- @treturn[1] bool Whether the transfer was successful or not -- @treturn[1] string The error, if applicable function ITEM:Transfer(invID, x, y, client, noReplication, isLogical) invID = invID or 0 if (self.invID == invID) then return false, "same inv" end local inventory = ix.item.inventories[invID] local curInv = ix.item.inventories[self.invID or 0] if (curInv and !IsValid(client)) then client = curInv.GetOwner and curInv:GetOwner() or nil end -- check if this item doesn't belong to another one of this player's characters local itemPlayerID = self:GetPlayerID() local itemCharacterID = self:GetCharacterID() if (!self.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then local playerID = client:SteamID64() local characterID = client:GetCharacter():GetID() if (itemPlayerID and itemCharacterID) then if (itemPlayerID == playerID and itemCharacterID != characterID) then return false, "itemOwned" end else self.characterID = characterID self.playerID = playerID local query = mysql:Update("ix_items") query:Update("character_id", characterID) query:Update("player_id", playerID) query:Where("item_id", self:GetID()) query:Execute() end end if (hook.Run("CanTransferItem", self, curInv, inventory) == false) then return false, "notAllowed" end local authorized = false if (inventory and inventory.OnAuthorizeTransfer and inventory:OnAuthorizeTransfer(client, curInv, self)) then authorized = true end if (!authorized and self.CanTransfer and self:CanTransfer(curInv, inventory) == false) then return false, "notAllowed" end if (curInv) then if (invID and invID > 0 and inventory) then local targetInv = inventory local bagInv if (!x and !y) then x, y, bagInv = inventory:FindEmptySlot(self.width, self.height) end if (bagInv) then targetInv = bagInv end if (!x or !y) then return false, "noFit" end local prevID = self.invID local status, result = targetInv:Add(self.id, nil, nil, x, y, noReplication) if (status) then if (self.invID > 0 and prevID != 0) then -- we are transferring this item from one inventory to another curInv:Remove(self.id, false, true, true) if (self.OnTransferred) then self:OnTransferred(curInv, inventory) end hook.Run("OnItemTransferred", self, curInv, inventory) return true elseif (self.invID > 0 and prevID == 0) then -- we are transferring this item from the world to an inventory ix.item.inventories[0][self.id] = nil if (self.OnTransferred) then self:OnTransferred(curInv, inventory) end hook.Run("OnItemTransferred", self, curInv, inventory) return true end else return false, result end elseif (IsValid(client)) then -- we are transferring this item from an inventory to the world self.invID = 0 curInv:Remove(self.id, false, true) local query = mysql:Update("ix_items") query:Update("inventory_id", 0) query:Where("item_id", self.id) query:Execute() inventory = ix.item.inventories[0] inventory[self:GetID()] = self if (self.OnTransferred) then self:OnTransferred(curInv, inventory) end hook.Run("OnItemTransferred", self, curInv, inventory) if (!isLogical) then return self:Spawn(client) end return true else return false, "noOwner" end else return false, "invalidInventory" end end end ix.meta.item = ITEM ================================================ FILE: gamemode/core/meta/sh_player.lua ================================================ --[[-- Physical representation of connected player. `Player`s are a type of `Entity`. They are a physical representation of a `Character` - and can possess at most one `Character` object at a time that you can interface with. See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Player) for all other methods that the `Player` class has. ]] -- @classmod Player local meta = FindMetaTable("Player") if (SERVER) then --- Returns the amount of time the player has played on the server. -- @realm shared -- @treturn number Number of seconds the player has played on the server function meta:GetPlayTime() return self.ixPlayTime + (RealTime() - (self.ixJoinTime or RealTime())) end else ix.playTime = ix.playTime or 0 function meta:GetPlayTime() return ix.playTime + (RealTime() - ix.joinTime or 0) end end --- Returns `true` if the player has their weapon raised. -- @realm shared -- @treturn bool Whether or not the player has their weapon raised function meta:IsWepRaised() return self:GetNetVar("raised", false) end --- Returns `true` if the player is restricted - that is to say that they are considered "bound" and cannot interact with -- objects normally (e.g hold weapons, use items, etc). An example of this would be a player in handcuffs. -- @realm shared -- @treturn bool Whether or not the player is restricted function meta:IsRestricted() return self:GetNetVar("restricted", false) end --- Returns `true` if the player is able to shoot their weapon. -- @realm shared -- @treturn bool Whether or not the player can shoot their weapon function meta:CanShootWeapon() return self:GetNetVar("canShoot", true) end local vectorLength2D = FindMetaTable("Vector").Length2D --- Returns `true` if the player is running. Running in this case means that their current speed is greater than their -- regularly set walk speed. -- @realm shared -- @treturn bool Whether or not the player is running function meta:IsRunning() return vectorLength2D(self:GetVelocity()) > (self:GetWalkSpeed() + 10) end --- Returns `true` if the player currently has a female model. This checks if the model has `female`, `alyx` or `mossman` in its -- name, or if the player's model class is `citizen_female`. -- @realm shared -- @treturn bool Whether or not the player has a female model function meta:IsFemale() local model = self:GetModel():lower() return (model:find("female") or model:find("alyx") or model:find("mossman")) != nil or ix.anim.GetModelClass(model) == "citizen_female" end --- Whether or not this player is stuck and cannot move. -- @realm shared -- @treturn bool Whether or not this player is stuck function meta:IsStuck() return util.TraceEntity({ start = self:GetPos(), endpos = self:GetPos(), filter = self }, self).StartSolid end --- Returns a good position in front of the player for an entity to be placed. This is usually used for item entities. -- @realm shared -- @entity entity Entity to get a position for -- @treturn vector Best guess for a good drop position in front of the player -- @usage local position = client:GetItemDropPos(entity) -- entity:SetPos(position) function meta:GetItemDropPos(entity) local data = {} local trace data.start = self:GetShootPos() data.endpos = self:GetShootPos() + self:GetAimVector() * 86 data.filter = self if (IsValid(entity)) then -- use a hull trace if there's a valid entity to avoid collisions local mins, maxs = entity:GetRotatedAABB(entity:OBBMins(), entity:OBBMaxs()) data.mins = mins data.maxs = maxs data.filter = {entity, self} trace = util.TraceHull(data) else -- trace along the normal for a few units so we can attempt to avoid a collision trace = util.TraceLine(data) data.start = trace.HitPos data.endpos = data.start + trace.HitNormal * 48 trace = util.TraceLine(data) end return trace.HitPos end --- Performs a time-delay action that requires this player to look at an entity. If this player looks away from the entity -- before the action timer completes, the action is cancelled. This is usually used in conjunction with `SetAction` to display -- progress to the player. -- @realm shared -- @entity entity that this player must look at -- @func callback Function to call when the timer completes -- @number time How much time in seconds this player must look at the entity for -- @func[opt=nil] onCancel Function to call when the timer has been cancelled -- @number[opt=96] distance Maximum distance a player can move away from the entity before the action is cancelled -- @see SetAction -- @usage client:SetAction("Searching...", 4) -- for displaying the progress bar -- client:DoStaredAction(entity, function() -- print("hello!") -- end) -- -- prints "hello!" after looking at the entity for 4 seconds function meta:DoStaredAction(entity, callback, time, onCancel, distance) local uniqueID = "ixStare"..self:SteamID64() local data = {} data.filter = self timer.Create(uniqueID, 0.1, time / 0.1, function() if (IsValid(self) and IsValid(entity)) then data.start = self:GetShootPos() data.endpos = data.start + self:GetAimVector()*(distance or 96) if (util.TraceLine(data).Entity != entity) then timer.Remove(uniqueID) if (onCancel) then onCancel() end elseif (callback and timer.RepsLeft(uniqueID) == 0) then callback() end else timer.Remove(uniqueID) if (onCancel) then onCancel() end end end) end --- Resets all bodygroups this player's model has to their defaults (`0`). -- @realm shared function meta:ResetBodygroups() for i = 0, (self:GetNumBodyGroups() - 1) do self:SetBodygroup(i, 0) end end if (SERVER) then util.AddNetworkString("ixActionBar") util.AddNetworkString("ixActionBarReset") util.AddNetworkString("ixStringRequest") --- Sets whether or not this player's current weapon is raised. -- @realm server -- @bool bState Whether or not the raise the weapon -- @entity[opt=GetActiveWeapon()] weapon Weapon to raise or lower. You should pass this argument if you already have a -- reference to this player's current weapon to avoid an expensive lookup for this player's current weapon. function meta:SetWepRaised(bState, weapon) weapon = weapon or self:GetActiveWeapon() if (IsValid(weapon)) then local bCanShoot = !bState and weapon.FireWhenLowered or bState self:SetNetVar("raised", bState) if (bCanShoot) then -- delay shooting while the raise animation is playing timer.Create("ixWeaponRaise" .. self:SteamID64(), 1, 1, function() if (IsValid(self)) then self:SetNetVar("canShoot", true) end end) else timer.Remove("ixWeaponRaise" .. self:SteamID64()) self:SetNetVar("canShoot", false) end else timer.Remove("ixWeaponRaise" .. self:SteamID64()) self:SetNetVar("raised", false) self:SetNetVar("canShoot", false) end end --- Inverts this player's weapon raised state. You should use `SetWepRaised` instead of this if you already have a reference -- to this player's current weapon. -- @realm server function meta:ToggleWepRaised() local weapon = self:GetActiveWeapon() if (!IsValid(weapon) or weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()] or weapon.IsAlwaysLowered or weapon.NeverRaised) then return end self:SetWepRaised(!self:IsWepRaised(), weapon) if (self:IsWepRaised() and weapon.OnRaised) then weapon:OnRaised() elseif (!self:IsWepRaised() and weapon.OnLowered) then weapon:OnLowered() end end --- Performs a delayed action that requires this player to hold use on an entity. This is displayed to this player as a -- closing ring over their crosshair. -- @realm server -- @number time How much time in seconds this player has to hold use for -- @entity entity Entity that this player must be looking at -- @func callback Function to run when the timer completes. It will be ran right away if `time` is `0`. Returning `false` in -- the callback will not mark this interaction as dirty if you're managing the interaction state manually. function meta:PerformInteraction(time, entity, callback) if (!IsValid(entity) or entity.ixInteractionDirty) then return end if (time > 0) then self.ixInteractionTarget = entity self.ixInteractionCharacter = self:GetCharacter():GetID() timer.Create("ixCharacterInteraction" .. self:SteamID(), time, 1, function() if (IsValid(self) and IsValid(entity) and IsValid(self.ixInteractionTarget) and self.ixInteractionCharacter == self:GetCharacter():GetID()) then local useEntity = self:GetUseEntity() if (IsValid(useEntity) and useEntity == self.ixInteractionTarget and !useEntity.ixInteractionDirty) then if (callback(self) != false) then useEntity.ixInteractionDirty = true end end end end) else if (callback(self) != false) then entity.ixInteractionDirty = true end end end --- Displays a progress bar for this player that takes the given amount of time to complete. -- @realm server -- @string text Text to display above the progress bar -- @number[opt=5] time How much time in seconds to wait before the timer completes -- @func callback Function to run once the timer completes -- @number[opt=CurTime()] startTime Game time in seconds that the timer started. If you are using `time`, then you shouldn't -- use this argument -- @number[opt=startTime + time] finishTime Game time in seconds that the timer should complete at. If you are using `time`, -- then you shouldn't use this argument function meta:SetAction(text, time, callback, startTime, finishTime) if (time and time <= 0) then if (callback) then callback(self) end return end -- Default the time to five seconds. time = time or 5 startTime = startTime or CurTime() finishTime = finishTime or (startTime + time) if (text == false) then timer.Remove("ixAct"..self:SteamID64()) net.Start("ixActionBarReset") net.Send(self) return end if (!text) then net.Start("ixActionBarReset") net.Send(self) else net.Start("ixActionBar") net.WriteFloat(startTime) net.WriteFloat(finishTime) net.WriteString(text) net.Send(self) end -- If we have provided a callback, run it delayed. if (callback) then -- Create a timer that runs once with a delay. timer.Create("ixAct"..self:SteamID64(), time, 1, function() -- Call the callback if the player is still valid. if (IsValid(self)) then callback(self) end end) end end --- Opens up a text box on this player's screen for input and returns the result. Remember to sanitize the user's input if -- it's needed! -- @realm server -- @string title Title to display on the panel -- @string subTitle Subtitle to display on the panel -- @func callback Function to run when this player enters their input. Callback is ran with the user's input string. -- @string[opt=nil] default Default value to put in the text box. -- @usage client:RequestString("Hello", "Please enter your name", function(text) -- client:ChatPrint("Hello, " .. text) -- end) -- -- prints "Hello, " in the player's chat function meta:RequestString(title, subTitle, callback, default) local time = math.floor(os.time()) self.ixStrReqs = self.ixStrReqs or {} self.ixStrReqs[time] = callback net.Start("ixStringRequest") net.WriteUInt(time, 32) net.WriteString(title) net.WriteString(subTitle) net.WriteString(default or "") net.Send(self) end --- Sets this player's restricted status. -- @realm server -- @bool bState Whether or not to restrict this player -- @bool bNoMessage Whether or not to suppress the restriction notification function meta:SetRestricted(bState, bNoMessage) if (bState) then self:SetNetVar("restricted", true) if (bNoMessage) then self:SetLocalVar("restrictNoMsg", true) end self.ixRestrictWeps = self.ixRestrictWeps or {} for _, v in ipairs(self:GetWeapons()) do self.ixRestrictWeps[#self.ixRestrictWeps + 1] = { class = v:GetClass(), item = v.ixItem, clip = v:Clip1() } v:Remove() end hook.Run("OnPlayerRestricted", self) else self:SetNetVar("restricted") if (self:GetLocalVar("restrictNoMsg")) then self:SetLocalVar("restrictNoMsg") end if (self.ixRestrictWeps) then for _, v in ipairs(self.ixRestrictWeps) do local weapon = self:Give(v.class, true) if (v.item) then weapon.ixItem = v.item end weapon:SetClip1(v.clip) end self.ixRestrictWeps = nil end hook.Run("OnPlayerUnRestricted", self) end end --- Creates a ragdoll entity of this player that will be synced with clients. This does **not** affect the player like -- `SetRagdolled` does. -- @realm server -- @bool[opt=false] bDontSetPlayer Whether or not to avoid setting the ragdoll's owning player -- @treturn entity Created ragdoll entity function meta:CreateServerRagdoll(bDontSetPlayer) local entity = ents.Create("prop_ragdoll") entity:SetPos(self:GetPos()) entity:SetAngles(self:EyeAngles()) entity:SetModel(self:GetModel()) entity:SetSkin(self:GetSkin()) for i = 0, (self:GetNumBodyGroups() - 1) do entity:SetBodygroup(i, self:GetBodygroup(i)) end entity:Spawn() if (!bDontSetPlayer) then entity:SetNetVar("player", self) end entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) entity:Activate() local velocity = self:GetVelocity() for i = 0, entity:GetPhysicsObjectCount() - 1 do local physObj = entity:GetPhysicsObjectNum(i) if (IsValid(physObj)) then physObj:SetVelocity(velocity) local index = entity:TranslatePhysBoneToBone(i) if (index) then local position, angles = self:GetBonePosition(index) physObj:SetPos(position) physObj:SetAngles(angles) end end end return entity end --- Sets this player's ragdoll status. -- @realm server -- @bool bState Whether or not to ragdoll this player -- @number[opt=0] time How long this player should stay ragdolled for. Set to `0` if they should stay ragdolled until they -- get back up manually -- @number[opt=5] getUpGrace How much time in seconds to wait before the player is able to get back up manually. Set to -- the same number as `time` to disable getting up manually entirely function meta:SetRagdolled(bState, time, getUpGrace) if (!self:Alive()) then return end getUpGrace = getUpGrace or time or 5 if (bState) then if (IsValid(self.ixRagdoll)) then self.ixRagdoll:Remove() end local entity = self:CreateServerRagdoll() entity:CallOnRemove("fixer", function() if (IsValid(self)) then self:SetLocalVar("blur", nil) self:SetLocalVar("ragdoll", nil) if (!entity.ixNoReset) then self:SetPos(entity:GetPos()) end self:SetNoDraw(false) self:SetNotSolid(false) self:SetMoveType(MOVETYPE_WALK) self:SetLocalVelocity(IsValid(entity) and entity.ixLastVelocity or vector_origin) end if (IsValid(self) and !entity.ixIgnoreDelete) then if (entity.ixWeapons) then for _, v in ipairs(entity.ixWeapons) do if (v.class) then local weapon = self:Give(v.class, true) if (v.item) then weapon.ixItem = v.item end self:SetAmmo(v.ammo, weapon:GetPrimaryAmmoType()) weapon:SetClip1(v.clip) elseif (v.item and v.invID == v.item.invID) then v.item:Equip(self, true, true) self:SetAmmo(v.ammo, self.carryWeapons[v.item.weaponCategory]:GetPrimaryAmmoType()) end end end if (entity.ixActiveWeapon) then if (self:HasWeapon(entity.ixActiveWeapon)) then self:SetActiveWeapon(self:GetWeapon(entity.ixActiveWeapon)) else local weapons = self:GetWeapons() if (#weapons > 0) then self:SetActiveWeapon(weapons[1]) end end end if (self:IsStuck()) then entity:DropToFloor() self:SetPos(entity:GetPos() + Vector(0, 0, 16)) local positions = ix.util.FindEmptySpace(self, {entity, self}) for _, v in ipairs(positions) do self:SetPos(v) if (!self:IsStuck()) then return end end end end end) self:SetLocalVar("blur", 25) self.ixRagdoll = entity entity.ixWeapons = {} entity.ixPlayer = self if (getUpGrace) then entity.ixGrace = CurTime() + getUpGrace end if (time and time > 0) then entity.ixStart = CurTime() entity.ixFinish = entity.ixStart + time self:SetAction("@wakingUp", nil, nil, entity.ixStart, entity.ixFinish) end if (IsValid(self:GetActiveWeapon())) then entity.ixActiveWeapon = self:GetActiveWeapon():GetClass() end for _, v in ipairs(self:GetWeapons()) do if (v.ixItem and v.ixItem.Equip and v.ixItem.Unequip) then entity.ixWeapons[#entity.ixWeapons + 1] = { item = v.ixItem, invID = v.ixItem.invID, ammo = self:GetAmmoCount(v:GetPrimaryAmmoType()) } v.ixItem:Unequip(self, false) else local clip = v:Clip1() local reserve = self:GetAmmoCount(v:GetPrimaryAmmoType()) entity.ixWeapons[#entity.ixWeapons + 1] = { class = v:GetClass(), item = v.ixItem, clip = clip, ammo = reserve } end end self:GodDisable() self:StripWeapons() self:SetMoveType(MOVETYPE_OBSERVER) self:SetNoDraw(true) self:SetNotSolid(true) local uniqueID = "ixUnRagdoll" .. self:SteamID() if (time) then timer.Create(uniqueID, 0.33, 0, function() if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then local velocity = entity:GetVelocity() entity.ixLastVelocity = velocity self:SetPos(entity:GetPos()) if (velocity:Length2D() >= 8) then if (!entity.ixPausing) then self:SetAction() entity.ixPausing = true end return elseif (entity.ixPausing) then self:SetAction("@wakingUp", time) entity.ixPausing = false end time = time - 0.33 if (time <= 0) then entity:Remove() end else timer.Remove(uniqueID) end end) else timer.Create(uniqueID, 0.33, 0, function() if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then self:SetPos(entity:GetPos()) else timer.Remove(uniqueID) end end) end self:SetLocalVar("ragdoll", entity:EntIndex()) hook.Run("OnCharacterFallover", self, entity, true) elseif (IsValid(self.ixRagdoll)) then self.ixRagdoll:Remove() hook.Run("OnCharacterFallover", self, nil, false) end end end ================================================ FILE: gamemode/core/meta/sh_tool.lua ================================================ local TOOL = ix.meta.tool or {} -- code replicated from gamemodes/sandbox/entities/weapons/gmod_tool/stool.lua function TOOL:Create() local object = {} setmetatable(object, self) self.__index = self object.Mode = nil object.SWEP = nil object.Owner = nil object.ClientConVar = {} object.ServerConVar = {} object.Objects = {} object.Stage = 0 object.Message = "start" object.LastMessage = 0 object.AllowedCVar = 0 return object end function TOOL:CreateConVars() local mode = self:GetMode() if (CLIENT) then for cvar, default in pairs(self.ClientConVar) do CreateClientConVar(mode .. "_" .. cvar, default, true, true) end return end -- Note: I changed this from replicated because replicated convars don't work when they're created via Lua. if (SERVER) then self.AllowedCVar = CreateConVar("toolmode_allow_" .. mode, 1, FCVAR_NOTIFY) end end function TOOL:GetServerInfo(property) local mode = self:GetMode() return GetConVarString(mode .. "_" .. property) end function TOOL:BuildConVarList() local mode = self:GetMode() local convars = {} for k, v in pairs(self.ClientConVar) do convars[mode .. "_" .. k] = v end return convars end function TOOL:GetClientInfo(property) return self:GetOwner():GetInfo(self:GetMode() .. "_" .. property) end function TOOL:GetClientNumber(property, default) return self:GetOwner():GetInfoNum(self:GetMode() .. "_" .. property, tonumber(default) or 0) end function TOOL:Allowed() if (CLIENT) then return true end return self.AllowedCVar:GetBool() end -- Now for all the TOOL redirects function TOOL:Init() end function TOOL:GetMode() return self.Mode end function TOOL:GetSWEP() return self.SWEP end function TOOL:GetOwner() return self:GetSWEP().Owner or self.Owner end function TOOL:GetWeapon() return self:GetSWEP().Weapon or self.Weapon end function TOOL:LeftClick() return false end function TOOL:RightClick() return false end function TOOL:Reload() self:ClearObjects() end function TOOL:Deploy() self:ReleaseGhostEntity() return end function TOOL:Holster() self:ReleaseGhostEntity() return end function TOOL:Think() self:ReleaseGhostEntity() end -- Checks the objects before any action is taken -- This is to make sure that the entities haven't been removed function TOOL:CheckObjects() for _, v in pairs(self.Objects) do if (!v.Ent:IsWorld() and !v.Ent:IsValid()) then self:ClearObjects() end end end ix.meta.tool = TOOL ================================================ FILE: gamemode/core/sh_commands.lua ================================================ ix.command.Add("Roll", { description = "@cmdRoll", arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, maximum) maximum = math.Clamp(maximum or 100, 0, 1000000) local value = math.random(0, maximum) ix.chat.Send(client, "roll", tostring(value), nil, nil, { max = maximum }) ix.log.Add(client, "roll", value, maximum) end }) ix.command.Add("Event", { description = "@cmdEvent", arguments = ix.type.text, superAdminOnly = true, OnRun = function(self, client, text) ix.chat.Send(client, "event", text) end }) ix.command.Add("PM", { description = "@cmdPM", arguments = { ix.type.player, ix.type.text }, OnRun = function(self, client, target, message) local voiceMail = target:GetData("vm") if (voiceMail and voiceMail:find("%S")) then return target:GetName()..": "..voiceMail end if ((client.ixNextPM or 0) < CurTime()) then ix.chat.Send(client, "pm", message, false, {client, target}, {target = target}) client.ixNextPM = CurTime() + 0.5 target.ixLastPM = client end end }) ix.command.Add("Reply", { description = "@cmdReply", arguments = ix.type.text, OnRun = function(self, client, message) local target = client.ixLastPM if (IsValid(target) and (client.ixNextPM or 0) < CurTime()) then ix.chat.Send(client, "pm", message, false, {client, target}, {target = target}) client.ixNextPM = CurTime() + 0.5 end end }) ix.command.Add("SetVoicemail", { description = "@cmdSetVoicemail", arguments = bit.bor(ix.type.text, ix.type.optional), OnRun = function(self, client, message) if (isstring(message) and message:find("%S")) then client:SetData("vm", message:utf8sub(1, 240)) return "@vmSet" else client:SetData("vm") return "@vmRem" end end }) ix.command.Add("CharGiveFlag", { description = "@cmdCharGiveFlag", privilege = "Manage Character Flags", superAdminOnly = true, arguments = { ix.type.character, bit.bor(ix.type.string, ix.type.optional) }, OnRun = function(self, client, target, flags) -- show string request if no flags are specified if (!flags) then local available = "" -- sort and display flags the character already has for k, _ in SortedPairs(ix.flag.list) do if (!target:HasFlags(k)) then available = available .. k end end return client:RequestString("@flagGiveTitle", "@cmdCharGiveFlag", function(text) ix.command.Run(client, "CharGiveFlag", {target:GetName(), text}) end, available) end target:GiveFlags(flags) for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("flagGive", client:GetName(), target:GetName(), flags) end end end }) ix.command.Add("CharTakeFlag", { description = "@cmdCharTakeFlag", privilege = "Manage Character Flags", superAdminOnly = true, arguments = { ix.type.character, bit.bor(ix.type.string, ix.type.optional) }, OnRun = function(self, client, target, flags) if (!flags) then return client:RequestString("@flagTakeTitle", "@cmdCharTakeFlag", function(text) ix.command.Run(client, "CharTakeFlag", {target:GetName(), text}) end, target:GetFlags()) end target:TakeFlags(flags) for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("flagTake", client:GetName(), flags, target:GetName()) end end end }) ix.command.Add("ToggleRaise", { description = "@cmdToggleRaise", OnRun = function(self, client, arguments) if (!timer.Exists("ixToggleRaise" .. client:SteamID())) then timer.Create("ixToggleRaise" .. client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function() client:ToggleWepRaised() end) end end }) ix.command.Add("CharSetModel", { description = "@cmdCharSetModel", superAdminOnly = true, arguments = { ix.type.character, ix.type.string }, OnRun = function(self, client, target, model) target:SetModel(model) target:GetPlayer():SetupHands() for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("cChangeModel", client:GetName(), target:GetName(), model) end end end }) ix.command.Add("CharSetSkin", { description = "@cmdCharSetSkin", adminOnly = true, arguments = { ix.type.character, bit.bor(ix.type.number, ix.type.optional) }, OnRun = function(self, client, target, skin) target:SetData("skin", skin) target:GetPlayer():SetSkin(skin or 0) for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("cChangeSkin", client:GetName(), target:GetName(), skin or 0) end end end }) ix.command.Add("CharSetBodygroup", { description = "@cmdCharSetBodygroup", adminOnly = true, arguments = { ix.type.character, ix.type.string, bit.bor(ix.type.number, ix.type.optional) }, OnRun = function(self, client, target, bodygroup, value) local index = target:GetPlayer():FindBodygroupByName(bodygroup) if (index > -1) then if (value and value < 1) then value = nil end local groups = target:GetData("groups", {}) groups[index] = value target:SetData("groups", groups) target:GetPlayer():SetBodygroup(index, value or 0) ix.util.NotifyLocalized("cChangeGroups", nil, client:GetName(), target:GetName(), bodygroup, value or 0) else return "@invalidArg", 2 end end }) ix.command.Add("CharSetAttribute", { description = "@cmdCharSetAttribute", privilege = "Manage Character Attributes", adminOnly = true, arguments = { ix.type.character, ix.type.string, ix.type.number }, OnRun = function(self, client, target, attributeName, level) for k, v in pairs(ix.attributes.list) do if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then target:SetAttrib(k, math.abs(level)) return "@attributeSet", target:GetName(), L(v.name, client), math.abs(level) end end return "@attributeNotFound" end }) ix.command.Add("CharAddAttribute", { description = "@cmdCharAddAttribute", privilege = "Manage Character Attributes", adminOnly = true, arguments = { ix.type.character, ix.type.string, ix.type.number }, OnRun = function(self, client, target, attributeName, level) for k, v in pairs(ix.attributes.list) do if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then target:UpdateAttrib(k, math.abs(level)) return "@attributeUpdate", target:GetName(), L(v.name, client), math.abs(level) end end return "@attributeNotFound" end }) ix.command.Add("CharSetName", { description = "@cmdCharSetName", adminOnly = true, arguments = { ix.type.character, bit.bor(ix.type.text, ix.type.optional) }, OnRun = function(self, client, target, newName) -- display string request panel if no name was specified if (newName:len() == 0) then return client:RequestString("@chgName", "@chgNameDesc", function(text) ix.command.Run(client, "CharSetName", {target:GetName(), text}) end, target:GetName()) end for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("cChangeName", client:GetName(), target:GetName(), newName) end end target:SetName(newName:gsub("#", "#​")) end }) ix.command.Add("CharGiveItem", { description = "@cmdCharGiveItem", superAdminOnly = true, arguments = { ix.type.character, ix.type.string, bit.bor(ix.type.number, ix.type.optional) }, OnRun = function(self, client, target, item, amount) local uniqueID = item:lower() if (!ix.item.list[uniqueID]) then for k, v in SortedPairs(ix.item.list) do if (ix.util.StringMatches(v.name, uniqueID)) then uniqueID = k break end end end amount = amount or 1 local bSuccess, error = target:GetInventory():Add(uniqueID, amount) if (bSuccess) then target:GetPlayer():NotifyLocalized("itemCreated") if (target != client:GetCharacter()) then return "@itemCreated" end else return "@" .. tostring(error) end end }) ix.command.Add("CharKick", { description = "@cmdCharKick", adminOnly = true, arguments = ix.type.character, OnRun = function(self, client, target) target:Save(function() target:Kick() end) for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("charKick", client:GetName(), target:GetName()) end end end }) ix.command.Add("CharBan", { description = "@cmdCharBan", privilege = "Ban Character", arguments = { ix.type.character, bit.bor(ix.type.number, ix.type.optional) }, adminOnly = true, OnRun = function(self, client, target, minutes) if (minutes) then minutes = minutes * 60 end target:Ban(minutes) target:Save() for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("charBan", client:GetName(), target:GetName()) end end end }) ix.command.Add("CharUnban", { description = "@cmdCharUnban", privilege = "Ban Character", arguments = ix.type.text, adminOnly = true, OnRun = function(self, client, name) if ((client.ixNextSearch or 0) >= CurTime()) then return L("charSearching", client) end for _, v in pairs(ix.char.loaded) do if (ix.util.StringMatches(v:GetName(), name)) then if (v:GetData("banned")) then v:SetData("banned") else return "@charNotBanned" end for _, v2 in player.Iterator() do if (self:OnCheckAccess(v2) or v2 == v:GetPlayer()) then v2:NotifyLocalized("charUnBan", client:GetName(), v:GetName()) end end return end end client.ixNextSearch = CurTime() + 15 local query = mysql:Select("ix_characters") query:Select("id") query:Select("name") query:Select("data") query:WhereLike("name", name) query:Limit(1) query:Callback(function(result) if (istable(result) and #result > 0) then local characterID = tonumber(result[1].id) local data = util.JSONToTable(result[1].data or "[]") name = result[1].name client.ixNextSearch = 0 if (!data.banned) then return client:NotifyLocalized("charNotBanned") end data.banned = nil local updateQuery = mysql:Update("ix_characters") updateQuery:Update("data", util.TableToJSON(data)) updateQuery:Where("id", characterID) updateQuery:Execute() for _, v in player.Iterator() do if (self:OnCheckAccess(v)) then v:NotifyLocalized("charUnBan", client:GetName(), name) end end end end) query:Execute() end }) do hook.Add("InitializedConfig", "ixMoneyCommands", function() local MONEY_NAME = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "") ix.command.Add("Give" .. MONEY_NAME, { alias = {"GiveMoney"}, description = "@cmdGiveMoney", arguments = ix.type.number, OnRun = function(self, client, amount) amount = math.floor(amount) if (amount <= 0) then return L("invalidArg", client, 1) end local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local target = util.TraceLine(data).Entity if (IsValid(target) and target:IsPlayer() and target:GetCharacter()) then if (!client:GetCharacter():HasMoney(amount)) then return end target:GetCharacter():GiveMoney(amount) client:GetCharacter():TakeMoney(amount) target:NotifyLocalized("moneyTaken", ix.currency.Get(amount)) client:NotifyLocalized("moneyGiven", ix.currency.Get(amount)) end end }) ix.command.Add("CharSet" .. MONEY_NAME, { alias = {"CharSetMoney"}, description = "@cmdCharSetMoney", superAdminOnly = true, arguments = { ix.type.character, ix.type.number }, OnRun = function(self, client, target, amount) amount = math.Round(amount) if (amount <= 0) then return "@invalidArg", 2 end target:SetMoney(amount) client:NotifyLocalized("setMoney", target:GetName(), ix.currency.Get(amount)) end }) ix.command.Add("Drop" .. MONEY_NAME, { alias = {"DropMoney"}, description = "@cmdDropMoney", arguments = ix.type.number, OnRun = function(self, client, amount) amount = math.Round(amount) local minDropAmount = ix.config.Get("minMoneyDropAmount", 1) if (amount < minDropAmount) then return "@belowMinMoneyDrop", minDropAmount end if (!client:GetCharacter():HasMoney(amount)) then return "@insufficientMoney" end client:GetCharacter():TakeMoney(amount) local money = ix.currency.Spawn(client, amount) money.ixCharID = client:GetCharacter():GetID() money.ixSteamID = client:SteamID() end }) end) end ix.command.Add("PlyWhitelist", { description = "@cmdPlyWhitelist", privilege = "Manage Character Whitelist", superAdminOnly = true, arguments = { ix.type.player, ix.type.text }, OnRun = function(self, client, target, name) if (name == "") then return "@invalidArg", 2 end local faction = ix.faction.teams[name] if (!faction) then for _, v in ipairs(ix.faction.indices) do if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then faction = v break end end end if (faction) then if (target:SetWhitelisted(faction.index, true)) then for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target) then v:NotifyLocalized("whitelist", client:GetName(), target:GetName(), L(faction.name, v)) end end end else return "@invalidFaction" end end }) ix.command.Add("CharGetUp", { description = "@cmdCharGetUp", OnRun = function(self, client, arguments) local entity = client.ixRagdoll if (IsValid(entity) and entity.ixGrace and entity.ixGrace < CurTime() and entity:GetVelocity():Length2D() < 8 and !entity.ixWakingUp) then entity.ixWakingUp = true entity:CallOnRemove("CharGetUp", function() client:SetAction() end) client:SetAction("@gettingUp", 5, function() if (!IsValid(entity)) then return end hook.Run("OnCharacterGetup", client, entity) entity:Remove() end) end end }) ix.command.Add("PlyUnwhitelist", { description = "@cmdPlyUnwhitelist", privilege = "Manage Character Whitelist", superAdminOnly = true, arguments = { ix.type.string, ix.type.text }, OnRun = function(self, client, target, name) local faction = ix.faction.teams[name] if (!faction) then for _, v in ipairs(ix.faction.indices) do if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then faction = v break end end end if (faction) then local targetPlayer = ix.util.FindPlayer(target) if (IsValid(targetPlayer) and targetPlayer:SetWhitelisted(faction.index, false)) then for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == targetPlayer) then v:NotifyLocalized("unwhitelist", client:GetName(), targetPlayer:GetName(), L(faction.name, v)) end end else local steamID64 = util.SteamIDTo64(target) local query = mysql:Select("ix_players") query:Select("data") query:Where("steamid", steamID64) query:Limit(1) query:Callback(function(result) if (istable(result) and #result > 0) then local data = util.JSONToTable(result[1].data or "[]") local whitelists = data.whitelists and data.whitelists[Schema.folder] if (!whitelists or !whitelists[faction.uniqueID]) then return end whitelists[faction.uniqueID] = nil local updateQuery = mysql:Update("ix_players") updateQuery:Update("data", util.TableToJSON(data)) updateQuery:Where("steamid", steamID64) updateQuery:Execute() for _, v in player.Iterator() do if (self:OnCheckAccess(v)) then v:NotifyLocalized("unwhitelist", client:GetName(), target, L(faction.name, v)) end end end end) query:Execute() end else return "@invalidFaction" end end }) ix.command.Add("CharFallOver", { description = "@cmdCharFallOver", arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, time) if (!client:Alive() or client:GetMoveType() == MOVETYPE_NOCLIP) then return "@notNow" end if (time and time > 0) then time = math.Clamp(time, 1, 60) end if (!IsValid(client.ixRagdoll)) then client:SetRagdolled(true, time) end end }) ix.command.Add("BecomeClass", { description = "@cmdBecomeClass", arguments = ix.type.text, OnRun = function(self, client, class) local character = client:GetCharacter() if (character) then local num = isnumber(tonumber(class)) and tonumber(class) or -1 if (ix.class.list[num]) then local v = ix.class.list[num] if (character:JoinClass(num)) then return "@becomeClass", L(v.name, client) else return "@becomeClassFail", L(v.name, client) end else for k, v in ipairs(ix.class.list) do if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(L(v.name, client), class)) then if (character:JoinClass(k)) then return "@becomeClass", L(v.name, client) else return "@becomeClassFail", L(v.name, client) end end end end return "@invalid", L("class", client) else return "@illegalAccess" end end }) ix.command.Add("CharDesc", { description = "@cmdCharDesc", arguments = bit.bor(ix.type.text, ix.type.optional), OnRun = function(self, client, description) if (!description:find("%S")) then return client:RequestString("@cmdCharDescTitle", "@cmdCharDescDescription", function(text) ix.command.Run(client, "CharDesc", {text}) end, client:GetCharacter():GetDescription()) end local info = ix.char.vars.description local result, fault, count = info:OnValidate(description) if (result == false) then return "@" .. fault, count end client:GetCharacter():SetDescription(description) return "@descChanged" end }) ix.command.Add("PlyTransfer", { description = "@cmdPlyTransfer", adminOnly = true, arguments = { ix.type.character, ix.type.text }, OnRun = function(self, client, target, name) local faction = ix.faction.teams[name] if (!faction) then for _, v in pairs(ix.faction.indices) do if (ix.util.StringMatches(L(v.name, client), name)) then faction = v break end end end if (faction) then local bHasWhitelist = target:GetPlayer():HasWhitelist(faction.index) if (bHasWhitelist) then target.vars.faction = faction.uniqueID target:SetFaction(faction.index) if (faction.OnTransferred) then faction:OnTransferred(target) end for _, v in player.Iterator() do if (self:OnCheckAccess(v) or v == target:GetPlayer()) then v:NotifyLocalized("cChangeFaction", client:GetName(), target:GetName(), L(faction.name, v)) end end else return "@charNotWhitelisted", target:GetName(), L(faction.name, client) end else return "@invalidFaction" end end }) ix.command.Add("CharSetClass", { description = "@cmdCharSetClass", adminOnly = true, arguments = { ix.type.character, ix.type.text }, OnRun = function(self, client, target, class) local classTable for _, v in ipairs(ix.class.list) do if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(v.name, class)) then classTable = v end end if (classTable) then local oldClass = target:GetClass() local targetPlayer = target:GetPlayer() if (targetPlayer:Team() == classTable.faction) then target:SetClass(classTable.index) hook.Run("PlayerJoinedClass", targetPlayer, classTable.index, oldClass) targetPlayer:NotifyLocalized("becomeClass", L(classTable.name, targetPlayer)) -- only send second notification if the character isn't setting their own class if (client != targetPlayer) then return "@setClass", target:GetName(), L(classTable.name, client) end else return "@invalidClassFaction" end else return "@invalidClass" end end }) ix.command.Add("MapRestart", { description = "@cmdMapRestart", adminOnly = true, arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, delay) delay = delay or 10 ix.util.NotifyLocalized("mapRestarting", nil, delay) timer.Simple(delay, function() RunConsoleCommand("changelevel", game.GetMap()) end) end }) ================================================ FILE: gamemode/core/sh_config.lua ================================================ --- Helper library for creating/setting config options. -- @module ix.config ix.config = ix.config or {} ix.config.stored = ix.config.stored or {} if (SERVER) then util.AddNetworkString("ixConfigList") util.AddNetworkString("ixConfigSet") util.AddNetworkString("ixConfigRequestUnloadedList") util.AddNetworkString("ixConfigUnloadedList") util.AddNetworkString("ixConfigPluginToggle") ix.config.server = ix.yaml.Read("gamemodes/helix/helix.yml") or {} end CAMI.RegisterPrivilege({ Name = "Helix - Manage Config", MinAccess = "superadmin" }) --- Creates a config option with the given information. -- @realm shared -- @string key Unique ID of the config -- @param value Default value that this config will have -- @string description Description of the config -- @func[opt=nil] callback Function to call when config is changed -- @tab[opt=nil] data Additional settings for this config option -- @bool[opt=false] bNoNetworking Whether or not to prevent networking the config -- @bool[opt=false] bSchemaOnly Whether or not the config is for the schema only function ix.config.Add(key, value, description, callback, data, bNoNetworking, bSchemaOnly) data = istable(data) and data or {} local oldConfig = ix.config.stored[key] local type = data.type or ix.util.GetTypeFromValue(value) if (!type) then ErrorNoHalt("attempted to add config with invalid type\n") return end local default = value data.type = nil -- using explicit nil comparisons so we don't get caught by a config's value being `false` if (oldConfig != nil) then if (oldConfig.value != nil) then value = oldConfig.value end if (oldConfig.default != nil) then default = oldConfig.default end end ix.config.stored[key] = { type = type, data = data, value = value, default = default, description = description, bNoNetworking = bNoNetworking, global = !bSchemaOnly, callback = callback, hidden = data.hidden or nil } end --- Sets the default value for a config option. -- @realm shared -- @string key Unique ID of the config -- @param value Default value for the config option function ix.config.SetDefault(key, value) local config = ix.config.stored[key] if (config) then config.default = value else -- set up dummy config if we're setting default of config that doesn't exist yet (i.e schema setting framework default) ix.config.stored[key] = { value = value, default = value } end end function ix.config.ForceSet(key, value, noSave) local config = ix.config.stored[key] if (config) then config.value = value end if (noSave) then ix.config.Save() end end --- Sets the value of a config option. -- @realm shared -- @string key Unique ID of the config -- @param value New value to assign to the config function ix.config.Set(key, value) local config = ix.config.stored[key] if (config) then local oldValue = value config.value = value if (SERVER) then if (!config.bNoNetworking) then net.Start("ixConfigSet") net.WriteString(key) net.WriteType(value) net.Broadcast() end if (config.callback) then config.callback(oldValue, value) end ix.config.Save() end end end --- Retrieves a value of a config option. If it is not set, it'll return the default that you've specified. -- @realm shared -- @string key Unique ID of the config -- @param default Default value to return if the config is not set -- @return Value associated with the key, or the default that was given if it doesn't exist function ix.config.Get(key, default) local config = ix.config.stored[key] -- ensure we aren't accessing a dummy value if (config and config.type) then if (config.value != nil) then return config.value elseif (config.default != nil) then return config.default end end return default end --- Loads all saved config options from disk. -- @realm shared -- @internal function ix.config.Load() if (SERVER) then local globals = ix.data.Get("config", nil, true, true) local data = ix.data.Get("config", nil, false, true) if (globals) then for k, v in pairs(globals) do ix.config.stored[k] = ix.config.stored[k] or {} ix.config.stored[k].value = v end end if (data) then for k, v in pairs(data) do ix.config.stored[k] = ix.config.stored[k] or {} ix.config.stored[k].value = v end end end ix.util.Include("helix/gamemode/config/sh_config.lua") if (SERVER or !IX_RELOADED) then hook.Run("InitializedConfig") end end if (SERVER) then function ix.config.GetChangedValues() local data = {} for k, v in pairs(ix.config.stored) do if (v.default != v.value) then data[k] = v.value end end return data end function ix.config.Send(client) net.Start("ixConfigList") net.WriteTable(ix.config.GetChangedValues()) net.Send(client) end --- Saves all config options to disk. -- @realm server -- @internal function ix.config.Save() local globals = {} local data = {} for k, v in pairs(ix.config.GetChangedValues()) do if (ix.config.stored[k].global) then globals[k] = v else data[k] = v end end -- Global and schema data set respectively. ix.data.Set("config", globals, true, true) ix.data.Set("config", data, false, true) end net.Receive("ixConfigSet", function(length, client) local key = net.ReadString() local value = net.ReadType() if (CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil) and type(ix.config.stored[key].default) == type(value)) then ix.config.Set(key, value) if (ix.util.IsColor(value)) then value = string.format("[%d, %d, %d]", value.r, value.g, value.b) elseif (istable(value)) then local value2 = "[" local count = table.Count(value) local i = 1 for _, v in SortedPairs(value) do value2 = value2 .. v .. (i == count and "]" or ", ") i = i + 1 end value = value2 elseif (isstring(value)) then value = string.format("\"%s\"", tostring(value)) elseif (isbool(value)) then value = string.format("[%s]", tostring(value)) end ix.util.NotifyLocalized("cfgSet", nil, client:Name(), key, tostring(value)) ix.log.Add(client, "cfgSet", key, value) end end) net.Receive("ixConfigRequestUnloadedList", function(length, client) if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then return end net.Start("ixConfigUnloadedList") net.WriteTable(ix.plugin.unloaded) net.Send(client) end) net.Receive("ixConfigPluginToggle", function(length, client) if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then return end local uniqueID = net.ReadString() local bUnloaded = !!ix.plugin.unloaded[uniqueID] local bShouldEnable = net.ReadBool() if ((bShouldEnable and bUnloaded) or (!bShouldEnable and !bUnloaded)) then ix.plugin.SetUnloaded(uniqueID, !bShouldEnable) -- flip bool since we're setting unloaded, not enabled ix.util.NotifyLocalized(bShouldEnable and "pluginLoaded" or "pluginUnloaded", nil, client:GetName(), uniqueID) ix.log.Add(client, bShouldEnable and "pluginLoaded" or "pluginUnloaded", uniqueID) net.Start("ixConfigPluginToggle") net.WriteString(uniqueID) net.WriteBool(bShouldEnable) net.Broadcast() end end) else net.Receive("ixConfigList", function() local data = net.ReadTable() for k, v in pairs(data) do if (ix.config.stored[k]) then ix.config.stored[k].value = v end end hook.Run("InitializedConfig", data) end) net.Receive("ixConfigSet", function() local key = net.ReadString() local value = net.ReadType() local config = ix.config.stored[key] if (config) then if (config.callback) then config.callback(config.value, value) end config.value = value local properties = ix.gui.properties if (IsValid(properties)) then local row = properties:GetCategory(L(config.data and config.data.category or "misc")):GetRow(key) if (IsValid(row)) then if (istable(value) and value.r and value.g and value.b) then value = Vector(value.r / 255, value.g / 255, value.b / 255) end row:SetValue(value) end end end end) net.Receive("ixConfigUnloadedList", function() ix.plugin.unloaded = net.ReadTable() ix.gui.bReceivedUnloadedPlugins = true if (IsValid(ix.gui.pluginManager)) then ix.gui.pluginManager:UpdateUnloaded() end end) net.Receive("ixConfigPluginToggle", function() local uniqueID = net.ReadString() local bEnabled = net.ReadBool() if (bEnabled) then ix.plugin.unloaded[uniqueID] = false else ix.plugin.unloaded[uniqueID] = true end if (IsValid(ix.gui.pluginManager)) then ix.gui.pluginManager:UpdatePlugin(uniqueID, bEnabled) end end) hook.Add("CreateMenuButtons", "ixConfig", function(tabs) if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Config", nil)) then return end tabs["config"] = { Create = function(info, container) container.panel = container:Add("ixConfigManager") end, OnSelected = function(info, container) container.panel.searchEntry:RequestFocus() end, Sections = { plugins = { Create = function(info, container) ix.gui.pluginManager = container:Add("ixPluginManager") end, OnSelected = function(info, container) ix.gui.pluginManager.searchEntry:RequestFocus() end } } } end) end ================================================ FILE: gamemode/core/sh_data.lua ================================================ --- Helper library for reading/writing files to the data folder. -- @module ix.data ix.data = ix.data or {} ix.data.stored = ix.data.stored or {} -- Create a folder to store data in. file.CreateDir("helix") --- Populates a file in the `data/helix` folder with some serialized data. -- @realm shared -- @string key Name of the file to save -- @param value Some sort of data to save -- @bool[opt=false] bGlobal Whether or not to write directly to the `data/helix` folder, or the `data/helix/schema` folder, -- where `schema` is the name of the current schema. -- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and save in the schema folder, rather than -- `data/helix/schema/map`, where `map` is the name of the current map. function ix.data.Set(key, value, bGlobal, bIgnoreMap) -- Get the base path to write to. local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/") -- Create the schema folder if the data is not global. if (!bGlobal) then file.CreateDir("helix/" .. Schema.folder .. "/") end -- If we're not ignoring the map, create a folder for the map. file.CreateDir(path) -- Write the data using JSON encoding. file.Write(path .. key .. ".txt", util.TableToJSON({value})) -- Cache the data value here. ix.data.stored[key] = value return path end --- Retrieves the contents of a saved file in the `data/helix` folder. -- @realm shared -- @string key Name of the file to load -- @param default Value to return if the file could not be loaded successfully -- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder, -- where `schema` is the name of the current schema. -- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and load from the schema folder, rather than -- `data/helix/schema/map`, where `map` is the name of the current map. -- @bool[opt=false] bRefresh Whether or not to skip the cache and forcefully load from disk. -- @return Value associated with the key, or the default that was given if it doesn't exists function ix.data.Get(key, default, bGlobal, bIgnoreMap, bRefresh) -- If it exists in the cache, return the cached value so it is faster. if (!bRefresh) then local stored = ix.data.stored[key] if (stored != nil) then return stored end end -- Get the path to read from. local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/") -- Read the data from a local file. local contents = file.Read(path .. key .. ".txt", "DATA") if (contents and contents != "") then local status, decoded = pcall(util.JSONToTable, contents) if (status and decoded) then local value = decoded[1] if (value != nil) then return value end end -- Backwards compatibility. -- This may be removed in the future. status, decoded = pcall(pon.decode, contents) if (status and decoded) then local value = decoded[1] if (value != nil) then return value end end end return default end --- Deletes the contents of a saved file in the `data/helix` folder. -- @realm shared -- @string key Name of the file to delete -- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder, -- where `schema` is the name of the current schema. -- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and delete from the schema folder, rather than -- `data/helix/schema/map`, where `map` is the name of the current map. -- @treturn bool Whether or not the deletion has succeeded function ix.data.Delete(key, bGlobal, bIgnoreMap) -- Get the path to read from. local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/") -- Read the data from a local file. local contents = file.Read(path .. key .. ".txt", "DATA") if (contents and contents != "") then file.Delete(path .. key .. ".txt") ix.data.stored[key] = nil return true end return false end if (SERVER) then timer.Create("ixSaveData", 600, 0, function() hook.Run("SaveData") end) end ================================================ FILE: gamemode/core/sh_util.lua ================================================ --- Various useful helper functions. -- @module ix.util ix.type = ix.type or { [2] = "string", [4] = "text", [8] = "number", [16] = "player", [32] = "steamid", [64] = "character", [128] = "bool", [1024] = "color", [2048] = "vector", string = 2, text = 4, number = 8, player = 16, steamid = 32, character = 64, bool = 128, color = 1024, vector = 2048, optional = 256, array = 512 } ix.blurRenderQueue = {} --- Includes a lua file based on the prefix of the file. This will automatically call `include` and `AddCSLuaFile` based on the -- current realm. This function should always be called shared to ensure that the client will receive the file from the server. -- @realm shared -- @string fileName Path of the Lua file to include. The path is relative to the file that is currently running this function -- @string[opt] realm Realm that this file should be included in. You should usually ignore this since it -- will be automatically be chosen based on the `SERVER` and `CLIENT` globals. This value should either be `"server"` or -- `"client"` if it is filled in manually function ix.util.Include(fileName, realm) if (!fileName) then error("[Helix] No file name specified for including.") end -- Only include server-side if we're on the server. if ((realm == "server" or fileName:find("sv_")) and SERVER) then return include(fileName) -- Shared is included by both server and client. elseif (realm == "shared" or fileName:find("shared.lua") or fileName:find("sh_")) then if (SERVER) then -- Send the file to the client if shared so they can run it. AddCSLuaFile(fileName) end return include(fileName) -- File is sent to client, included on client. elseif (realm == "client" or fileName:find("cl_")) then if (SERVER) then AddCSLuaFile(fileName) else return include(fileName) end end end --- Includes multiple files in a directory. -- @realm shared -- @string directory Directory to include files from -- @bool[opt] bFromLua Whether or not to search from the base `lua/` folder, instead of contextually basing from `schema/` -- or `gamemode/` -- @see ix.util.Include function ix.util.IncludeDir(directory, bFromLua) -- By default, we include relatively to Helix. local baseDir = "helix" -- If we're in a schema, include relative to the schema. if (Schema and Schema.folder and Schema.loading) then baseDir = Schema.folder.."/schema/" else baseDir = baseDir.."/gamemode/" end -- Find all of the files within the directory. for _, v in ipairs(file.Find((bFromLua and "" or baseDir)..directory.."/*.lua", "LUA")) do -- Include the file from the prefix. ix.util.Include(directory.."/"..v) end end --- Removes the realm prefix from a file name. The returned string will be unchanged if there is no prefix found. -- @realm shared -- @string name String to strip prefix from -- @treturn string String stripped of prefix -- @usage print(ix.util.StripRealmPrefix("sv_init.lua")) -- > init.lua function ix.util.StripRealmPrefix(name) local prefix = name:sub(1, 3) return (prefix == "sh_" or prefix == "sv_" or prefix == "cl_") and name:sub(4) or name end --- Returns `true` if the given input is a color table. This is necessary since the engine `IsColor` function only checks for -- color metatables - which are not used for regular Lua color types. -- @realm shared -- @param input Input to check -- @treturn bool Whether or not the input is a color function ix.util.IsColor(input) return istable(input) and isnumber(input.a) and isnumber(input.g) and isnumber(input.b) and (input.a and isnumber(input.a) or input.a == nil) end --- Returns a dimmed version of the given color by the given scale. -- @realm shared -- @color color Color to dim -- @number multiplier What to multiply the red, green, and blue values by -- @number[opt=255] alpha Alpha to use in dimmed color -- @treturn color Dimmed color -- @usage print(ix.util.DimColor(Color(100, 100, 100, 255), 0.5)) -- > 50 50 50 255 function ix.util.DimColor(color, multiplier, alpha) return Color(color.r * multiplier, color.g * multiplier, color.b * multiplier, alpha or 255) end --- Sanitizes an input value with the given type. This function ensures that a valid type is always returned. If a valid value -- could not be found, it will return the default value for the type. This only works for simple types - e.g it does not work -- for player, character, or Steam ID types. -- @realm shared -- @ixtype type Type to check for -- @param input Value to sanitize -- @return Sanitized value -- @see ix.type -- @usage print(ix.util.SanitizeType(ix.type.number, "123")) -- > 123 -- print(ix.util.SanitizeType(ix.type.bool, 1)) -- > true function ix.util.SanitizeType(type, input) if (type == ix.type.string) then return tostring(input) elseif (type == ix.type.text) then return tostring(input) elseif (type == ix.type.number) then return tonumber(input or 0) or 0 elseif (type == ix.type.bool) then return tobool(input) elseif (type == ix.type.color) then return istable(input) and Color(tonumber(input.r) or 255, tonumber(input.g) or 255, tonumber(input.b) or 255, tonumber(input.a) or 255) or color_white elseif (type == ix.type.vector) then return isvector(input) and input or vector_origin elseif (type == ix.type.array) then return input else error("attempted to sanitize " .. (ix.type[type] and ("invalid type " .. ix.type[type]) or "unknown type " .. type)) end end do local typeMap = { string = ix.type.string, number = ix.type.number, Player = ix.type.player, boolean = ix.type.bool, Vector = ix.type.vector } local tableMap = { [ix.type.character] = function(value) return getmetatable(value) == ix.meta.character end, [ix.type.color] = function(value) return ix.util.IsColor(value) end, [ix.type.steamid] = function(value) return isstring(value) and (value:match("STEAM_(%d+):(%d+):(%d+)")) != nil end } --- Returns the `ix.type` of the given value. -- @realm shared -- @param value Value to get the type of -- @treturn ix.type Type of value -- @see ix.type -- @usage print(ix.util.GetTypeFromValue("hello")) -- > 2 -- i.e the value of ix.type.string function ix.util.GetTypeFromValue(value) local result = typeMap[type(value)] if (result) then return result end if (istable(value)) then for k, v in pairs(tableMap) do if (v(value)) then return k end end end end end function ix.util.Bind(self, callback) return function(_, ...) return callback(self, ...) end end -- Returns the address:port of the server. function ix.util.GetAddress() return game.GetIPAddress() end --- Returns a cached copy of the given material, or creates and caches one if it doesn't exist. This is a quick helper function -- if you aren't locally storing a `Material()` call. -- @realm shared -- @string materialPath Path to the material -- @treturn[1] material The cached material -- @treturn[2] nil If the material doesn't exist in the filesystem function ix.util.GetMaterial(materialPath) -- Cache the material. ix.util.cachedMaterials = ix.util.cachedMaterials or {} ix.util.cachedMaterials[materialPath] = ix.util.cachedMaterials[materialPath] or Material(materialPath) return ix.util.cachedMaterials[materialPath] end --- Attempts to find a player by matching their name or Steam ID. -- @realm shared -- @string identifier Search query -- @bool[opt=false] bAllowPatterns Whether or not to accept Lua patterns in `identifier` -- @treturn player Player that matches the given search query - this will be `nil` if a player could not be found function ix.util.FindPlayer(identifier, bAllowPatterns) if (#identifier == 0) then return end if (string.find(identifier, "STEAM_(%d+):(%d+):(%d+)")) then return player.GetBySteamID(identifier) end if (!bAllowPatterns) then identifier = string.PatternSafe(identifier) end for _, v in player.Iterator() do if (ix.util.StringMatches(v:Name(), identifier)) then return v end end end --- Checks to see if two strings are equivalent using a fuzzy manner. Both strings will be lowered, and will return `true` if -- the strings are identical, or if `b` is a substring of `a`. -- @realm shared -- @string a First string to check -- @string b Second string to check -- @treturn bool Whether or not the strings are equivalent function ix.util.StringMatches(a, b) if (a and b) then local a2, b2 = a:utf8lower(), b:utf8lower() -- Check if the actual letters match. if (a == b) then return true end if (a2 == b2) then return true end -- Be less strict and search. if (a:find(b)) then return true end if (a2:find(b2)) then return true end end return false end --- Returns a string that has the named arguments in the format string replaced with the given arguments. -- @realm shared -- @string format Format string -- @tparam tab|... Arguments to pass to the formatted string. If passed a table, it will use that table as the lookup table for -- the named arguments. If passed multiple arguments, it will replace the arguments in the string in order. -- @usage print(ix.util.FormatStringNamed("Hi, my name is {name}.", {name = "Bobby"})) -- > Hi, my name is Bobby. -- @usage print(ix.util.FormatStringNamed("Hi, my name is {name}.", "Bobby")) -- > Hi, my name is Bobby. function ix.util.FormatStringNamed(format, ...) local arguments = {...} local bArray = false -- Whether or not the input has numerical indices or named ones local input -- If the first argument is a table, we can assumed it's going to specify which -- keys to fill out. Otherwise we'll fill in specified arguments in order. if (istable(arguments[1])) then input = arguments[1] else input = arguments bArray = true end local i = 0 local result = format:gsub("{(%w-)}", function(word) i = i + 1 return tostring((bArray and input[i] or input[word]) or word) end) return result end do local upperMap = { ["ooc"] = true, ["looc"] = true, ["afk"] = true, ["url"] = true } --- Returns a string that is the given input with spaces in between each CamelCase word. This function will ignore any words -- that do not begin with a capital letter. The words `ooc`, `looc`, `afk`, and `url` will be automatically transformed -- into uppercase text. This will not capitalize non-ASCII letters due to limitations with Lua's pattern matching. -- @realm shared -- @string input String to expand -- @bool[opt=false] bNoUpperFirst Whether or not to avoid capitalizing the first character. This is useful for lowerCamelCase -- @treturn string Expanded CamelCase string -- @usage print(ix.util.ExpandCamelCase("HelloWorld")) -- > Hello World function ix.util.ExpandCamelCase(input, bNoUpperFirst) input = bNoUpperFirst and input or input:utf8sub(1, 1):utf8upper() .. input:utf8sub(2) -- extra parentheses to select first return value of gsub return string.TrimRight((input:gsub("%u%l+", function(word) if (upperMap[word:utf8lower()]) then word = word:utf8upper() end return word .. " " end))) end end function ix.util.GridVector(vec, gridSize) if (gridSize <= 0) then gridSize = 1 end for i = 1, 3 do vec[i] = vec[i] / gridSize vec[i] = math.Round(vec[i]) vec[i] = vec[i] * gridSize end return vec end do local i local value local character local function iterator(table) repeat i = i + 1 value = table[i] character = value and value:GetCharacter() until character or value == nil return value, character end --- Returns an iterator for characters. The resulting key/values will be a player and their corresponding characters. This -- iterator skips over any players that do not have a valid character loaded. -- @realm shared -- @treturn Iterator -- @usage for client, character in ix.util.GetCharacters() do -- print(client, character) -- end -- > Player [1][Bot01] character[1] -- > Player [2][Bot02] character[2] -- -- etc. function ix.util.GetCharacters() i = 0 return iterator, player.GetAll() end end if (CLIENT) then local blur = ix.util.GetMaterial("pp/blurscreen") local surface = surface --- Blurs the content underneath the given panel. This will fall back to a simple darkened rectangle if the player has -- blurring disabled. -- @realm client -- @tparam panel panel Panel to draw the blur for -- @number[opt=5] amount Intensity of the blur. This should be kept between 0 and 10 for performance reasons -- @number[opt=0.2] passes Quality of the blur. This should be kept as default -- @number[opt=255] alpha Opacity of the blur -- @usage function PANEL:Paint(width, height) -- ix.util.DrawBlur(self) -- end function ix.util.DrawBlur(panel, amount, passes, alpha) amount = amount or 5 if (ix.option.Get("cheapBlur", false)) then surface.SetDrawColor(50, 50, 50, alpha or (amount * 20)) surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall()) else surface.SetMaterial(blur) surface.SetDrawColor(255, 255, 255, alpha or 255) local x, y = panel:LocalToScreen(0, 0) for i = -(passes or 0.2), 1, 0.2 do -- Do things to the blur material to make it blurry. blur:SetFloat("$blur", i * amount) blur:Recompute() -- Draw the blur material over the screen. render.UpdateScreenEffectTexture() surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) end end end --- Draws a blurred rectangle with the given position and bounds. This shouldn't be used for panels, see `ix.util.DrawBlur` -- instead. -- @realm client -- @number x X-position of the rectangle -- @number y Y-position of the rectangle -- @number width Width of the rectangle -- @number height Height of the rectangle -- @number[opt=5] amount Intensity of the blur. This should be kept between 0 and 10 for performance reasons -- @number[opt=0.2] passes Quality of the blur. This should be kept as default -- @number[opt=255] alpha Opacity of the blur -- @usage hook.Add("HUDPaint", "MyHUDPaint", function() -- ix.util.DrawBlurAt(0, 0, ScrW(), ScrH()) -- end) function ix.util.DrawBlurAt(x, y, width, height, amount, passes, alpha) amount = amount or 5 if (ix.option.Get("cheapBlur", false)) then surface.SetDrawColor(30, 30, 30, amount * 20) surface.DrawRect(x, y, width, height) else surface.SetMaterial(blur) surface.SetDrawColor(255, 255, 255, alpha or 255) local scrW, scrH = ScrW(), ScrH() local x2, y2 = x / scrW, y / scrH local w2, h2 = (x + width) / scrW, (y + height) / scrH for i = -(passes or 0.2), 1, 0.2 do blur:SetFloat("$blur", i * amount) blur:Recompute() render.UpdateScreenEffectTexture() surface.DrawTexturedRectUV(x, y, width, height, x2, y2, w2, h2) end end end --- Pushes a 3D2D blur to be rendered in the world. The draw function will be called next frame in the -- `PostDrawOpaqueRenderables` hook. -- @realm client -- @func drawFunc Function to call when it needs to be drawn function ix.util.PushBlur(drawFunc) ix.blurRenderQueue[#ix.blurRenderQueue + 1] = drawFunc end --- Draws some text with a shadow. -- @realm client -- @string text Text to draw -- @number x X-position of the text -- @number y Y-position of the text -- @color color Color of the text to draw -- @number[opt=TEXT_ALIGN_LEFT] alignX Horizontal alignment of the text, using one of the `TEXT_ALIGN_*` constants -- @number[opt=TEXT_ALIGN_LEFT] alignY Vertical alignment of the text, using one of the `TEXT_ALIGN_*` constants -- @string[opt="ixGenericFont"] font Font to use for the text -- @number[opt=color.a * 0.575] alpha Alpha of the shadow function ix.util.DrawText(text, x, y, color, alignX, alignY, font, alpha) color = color or color_white return draw.TextShadow({ text = text, font = font or "ixGenericFont", pos = {x, y}, color = color, xalign = alignX or TEXT_ALIGN_LEFT, yalign = alignY or TEXT_ALIGN_LEFT }, 1, alpha or (color.a * 0.575)) end --- Wraps text so it does not pass a certain width. This function will try and break lines between words if it can, -- otherwise it will break a word if it's too long. -- @realm client -- @string text Text to wrap -- @number maxWidth Maximum allowed width in pixels -- @string[opt="ixChatFont"] font Font to use for the text function ix.util.WrapText(text, maxWidth, font) font = font or "ixChatFont" surface.SetFont(font) local words = string.Explode("%s", text, true) local lines = {} local line = "" local lineWidth = 0 -- luacheck: ignore 231 -- we don't need to calculate wrapping if we're under the max width if (surface.GetTextSize(text) <= maxWidth) then return {text} end for i = 1, #words do local word = words[i] local wordWidth = surface.GetTextSize(word) -- this word is very long so we have to split it by character if (wordWidth > maxWidth) then local newWidth for i2 = 1, word:utf8len() do local character = word[i2] newWidth = surface.GetTextSize(line .. character) -- if current line + next character is too wide, we'll shove the next character onto the next line if (newWidth > maxWidth) then lines[#lines + 1] = line line = "" end line = line .. character end lineWidth = newWidth continue end local space = (i == 1) and "" or " " local newLine = line .. space .. word local newWidth = surface.GetTextSize(newLine) if (newWidth > maxWidth) then -- adding this word will bring us over the max width lines[#lines + 1] = line line = word lineWidth = wordWidth else -- otherwise we tack on the new word and continue line = newLine lineWidth = newWidth end end if (line != "") then lines[#lines + 1] = line end return lines end local cos, sin, abs, rad1, log, pow = math.cos, math.sin, math.abs, math.rad, math.log, math.pow -- arc drawing functions -- by bobbleheadbob -- https://facepunch.com/showthread.php?t=1558060 function ix.util.DrawArc(cx, cy, radius, thickness, startang, endang, roughness, color) surface.SetDrawColor(color) ix.util.DrawPrecachedArc(ix.util.PrecacheArc(cx, cy, radius, thickness, startang, endang, roughness)) end function ix.util.DrawPrecachedArc(arc) -- Draw a premade arc. for _, v in ipairs(arc) do surface.DrawPoly(v) end end function ix.util.PrecacheArc(cx, cy, radius, thickness, startang, endang, roughness) local quadarc = {} -- Correct start/end ang startang = startang or 0 endang = endang or 0 -- Define step -- roughness = roughness or 1 local diff = abs(startang - endang) local smoothness = log(diff, 2) / 2 local step = diff / (pow(2, smoothness)) if startang > endang then step = abs(step) * -1 end -- Create the inner circle's points. local inner = {} local outer = {} local ct = 1 local r = radius - thickness for deg = startang, endang, step do local rad = rad1(deg) local cosrad, sinrad = cos(rad), sin(rad) --calculate sin, cos local ox, oy = cx + (cosrad * r), cy + (-sinrad * r) --apply to inner distance inner[ct] = { x = ox, y = oy, u = (ox - cx) / radius + .5, v = (oy - cy) / radius + .5 } local ox2, oy2 = cx + (cosrad * radius), cy + (-sinrad * radius) --apply to outer distance outer[ct] = { x = ox2, y = oy2, u = (ox2 - cx) / radius + .5, v = (oy2 - cy) / radius + .5 } ct = ct + 1 end -- QUAD the points. for tri = 1, ct do local p1, p2, p3, p4 local t = tri + 1 p1 = outer[tri] p2 = outer[t] p3 = inner[t] p4 = inner[tri] quadarc[tri] = {p1, p2, p3, p4} end -- Return a table of triangles to draw. return quadarc end --- Resets all stencil values to known good (i.e defaults) -- @realm client function ix.util.ResetStencilValues() render.SetStencilWriteMask(0xFF) render.SetStencilTestMask(0xFF) render.SetStencilReferenceValue(0) render.SetStencilCompareFunction(STENCIL_ALWAYS) render.SetStencilPassOperation(STENCIL_KEEP) render.SetStencilFailOperation(STENCIL_KEEP) render.SetStencilZFailOperation(STENCIL_KEEP) render.ClearStencil() end -- luacheck: globals derma -- Alternative to SkinHook that allows you to pass more arguments to skin methods function derma.SkinFunc(name, panel, a, b, c, d, e, f, g) local skin = (ispanel(panel) and IsValid(panel)) and panel:GetSkin() or derma.GetDefaultSkin() if (!skin) then return end local func = skin[name] if (!func) then return end return func(skin, panel, a, b, c, d, e, f, g) end -- Alternative to Color that retrieves from the SKIN.Colours table function derma.GetColor(name, panel, default) default = default or ix.config.Get("color") local skin = panel:GetSkin() if (!skin) then return default end return skin.Colours[name] or default end hook.Add("OnScreenSizeChanged", "ix.OnScreenSizeChanged", function(oldWidth, oldHeight) hook.Run("ScreenResolutionChanged", oldWidth, oldHeight) end) end -- Vector extension, courtesy of code_gs do local VECTOR = FindMetaTable("Vector") local CrossProduct = VECTOR.Cross local right = Vector(0, -1, 0) function VECTOR:Right(vUp) if (self[1] == 0 and self[2] == 0) then return right end if (vUp == nil) then vUp = vector_up end local vRet = CrossProduct(self, vUp) vRet:Normalize() return vRet end function VECTOR:Up(vUp) if (self[1] == 0 and self[2] == 0) then return Vector(-self[3], 0, 0) end if (vUp == nil) then vUp = vector_up end local vRet = CrossProduct(self, vUp) vRet = CrossProduct(vRet, self) vRet:Normalize() return vRet end end -- luacheck: globals FCAP_IMPULSE_USE FCAP_CONTINUOUS_USE FCAP_ONOFF_USE -- luacheck: globals FCAP_DIRECTIONAL_USE FCAP_USE_ONGROUND FCAP_USE_IN_RADIUS FCAP_IMPULSE_USE = 0x00000010 FCAP_CONTINUOUS_USE = 0x00000020 FCAP_ONOFF_USE = 0x00000040 FCAP_DIRECTIONAL_USE = 0x00000080 FCAP_USE_ONGROUND = 0x00000100 FCAP_USE_IN_RADIUS = 0x00000200 function ix.util.IsUseableEntity(entity, requiredCaps) if (IsValid(entity)) then local caps = entity:ObjectCaps() if (bit.band(caps, bit.bor(FCAP_IMPULSE_USE, FCAP_CONTINUOUS_USE, FCAP_ONOFF_USE, FCAP_DIRECTIONAL_USE))) then if (bit.band(caps, requiredCaps) == requiredCaps) then return true end end end end do local function IntervalDistance(x, x0, x1) -- swap so x0 < x1 if (x0 > x1) then local tmp = x0 x0 = x1 x1 = tmp end if (x < x0) then return x0-x elseif (x > x1) then return x - x1 end return 0 end local NUM_TANGENTS = 8 local tangents = {0, 1, 0.57735026919, 0.3639702342, 0.267949192431, 0.1763269807, -0.1763269807, -0.267949192431} local traceMin = Vector(-16, -16, -16) local traceMax = Vector(16, 16, 16) function ix.util.FindUseEntity(player, origin, forward) local tr local up = forward:Up() -- Search for objects in a sphere (tests for entities that are not solid, yet still useable) local searchCenter = origin -- NOTE: Some debris objects are useable too, so hit those as well -- A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. local useableContents = bit.bor(MASK_SOLID, CONTENTS_DEBRIS, CONTENTS_PLAYERCLIP) -- UNDONE: Might be faster to just fold this range into the sphere query local pObject local nearestDist = 1e37 -- try the hit entity if there is one, or the ground entity if there isn't. local pNearest = NULL for i = 1, NUM_TANGENTS do if (i == 0) then tr = util.TraceLine({ start = searchCenter, endpos = searchCenter + forward * 1024, mask = useableContents, filter = player }) tr.EndPos = searchCenter + forward * 1024 else local down = forward - tangents[i] * up down:Normalize() tr = util.TraceHull({ start = searchCenter, endpos = searchCenter + down * 72, mins = traceMin, maxs = traceMax, mask = useableContents, filter = player }) tr.EndPos = searchCenter + down * 72 end pObject = tr.Entity local bUsable = ix.util.IsUseableEntity(pObject, 0) while (IsValid(pObject) and !bUsable and pObject:GetMoveParent()) do pObject = pObject:GetMoveParent() bUsable = ix.util.IsUseableEntity(pObject, 0) end if (bUsable) then local delta = tr.EndPos - tr.StartPos local centerZ = origin.z - player:WorldSpaceCenter().z delta.z = IntervalDistance(tr.EndPos.z, centerZ - player:OBBMins().z, centerZ + player:OBBMaxs().z) local dist = delta:Length() if (dist < 80) then pNearest = pObject -- if this is directly under the cursor just return it now if (i == 0) then return pObject end end end end -- check ground entity first -- if you've got a useable ground entity, then shrink the cone of this search to 45 degrees -- otherwise, search out in a 90 degree cone (hemisphere) if (IsValid(player:GetGroundEntity()) and ix.util.IsUseableEntity(player:GetGroundEntity(), FCAP_USE_ONGROUND)) then pNearest = player:GetGroundEntity() end if (IsValid(pNearest)) then -- estimate nearest object by distance from the view vector local point = pNearest:NearestPoint(searchCenter) nearestDist = util.DistanceToLine(searchCenter, forward, point) end for _, v in ipairs(ents.FindInSphere(searchCenter, 80)) do if (!ix.util.IsUseableEntity(v, FCAP_USE_IN_RADIUS)) then continue end -- see if it's more roughly in front of the player than previous guess local point = v:NearestPoint(searchCenter) local dir = point - searchCenter dir:Normalize() local dot = dir:Dot(forward) -- Need to be looking at the object more or less if (dot < 0.8) then continue end local dist = util.DistanceToLine(searchCenter, forward, point) if (dist < nearestDist) then -- Since this has purely been a radius search to this point, we now -- make sure the object isn't behind glass or a grate. local trCheckOccluded = {} util.TraceLine({ start = searchCenter, endpos = point, mask = useableContents, filter = player, output = trCheckOccluded }) if (trCheckOccluded.fraction == 1.0 or trCheckOccluded.Entity == v) then pNearest = v nearestDist = dist end end end return pNearest end end ALWAYS_RAISED = {} ALWAYS_RAISED["weapon_physgun"] = true ALWAYS_RAISED["gmod_tool"] = true ALWAYS_RAISED["ix_poshelper"] = true function ix.util.FindEmptySpace(entity, filter, spacing, size, height, tolerance) spacing = spacing or 32 size = size or 3 height = height or 36 tolerance = tolerance or 5 local position = entity:GetPos() local mins, maxs = Vector(-spacing * 0.5, -spacing * 0.5, 0), Vector(spacing * 0.5, spacing * 0.5, height) local output = {} for x = -size, size do for y = -size, size do local origin = position + Vector(x * spacing, y * spacing, 0) local data = {} data.start = origin + mins + Vector(0, 0, tolerance) data.endpos = origin + maxs data.filter = filter or entity local trace = util.TraceLine(data) data.start = origin + Vector(-maxs.x, -maxs.y, tolerance) data.endpos = origin + Vector(mins.x, mins.y, height) local trace2 = util.TraceLine(data) if (trace.StartSolid or trace.Hit or trace2.StartSolid or trace2.Hit or !util.IsInWorld(origin)) then continue end output[#output + 1] = origin end end table.sort(output, function(a, b) return a:DistToSqr(position) < b:DistToSqr(position) end) return output end -- Time related stuff. do --- Gets the current time in the UTC time-zone. -- @realm shared -- @treturn number Current time in UTC function ix.util.GetUTCTime() local date = os.date("!*t") local localDate = os.date("*t") localDate.isdst = false return os.difftime(os.time(date), os.time(localDate)) end -- Setup for time strings. local TIME_UNITS = {} TIME_UNITS["s"] = 1 -- Seconds TIME_UNITS["m"] = 60 -- Minutes TIME_UNITS["h"] = 3600 -- Hours TIME_UNITS["d"] = TIME_UNITS["h"] * 24 -- Days TIME_UNITS["w"] = TIME_UNITS["d"] * 7 -- Weeks TIME_UNITS["mo"] = TIME_UNITS["d"] * 30 -- Months TIME_UNITS["y"] = TIME_UNITS["d"] * 365 -- Years --- Gets the amount of seconds from a given formatted string. If no time units are specified, it is assumed minutes. -- The valid values are as follows: -- -- - `s` - Seconds -- - `m` - Minutes -- - `h` - Hours -- - `d` - Days -- - `w` - Weeks -- - `mo` - Months -- - `y` - Years -- @realm shared -- @string text Text to interpret a length of time from -- @treturn[1] number Amount of seconds from the length interpreted from the given string -- @treturn[2] 0 If the given string does not have a valid time -- @usage print(ix.util.GetStringTime("5y2d7w")) -- > 162086400 -- 5 years, 2 days, 7 weeks function ix.util.GetStringTime(text) local minutes = tonumber(text) if (minutes) then return math.abs(minutes * 60) end local time = 0 for amount, unit in text:lower():gmatch("(%d+)(%a+)") do amount = tonumber(amount) if (amount and TIME_UNITS[unit]) then time = time + math.abs(amount * TIME_UNITS[unit]) end end return time end end --[[ Credit to TFA for figuring this mess out. Original: https://steamcommunity.com/sharedfiles/filedetails/?id=903541818 ]] if (system.IsLinux()) then local cache = {} -- Helper Functions local function GetSoundPath(path, gamedir) if (!gamedir) then path = "sound/" .. path gamedir = "GAME" end return path, gamedir end local function f_IsWAV(f) f:Seek(8) return f:Read(4) == "WAVE" end -- WAV functions local function f_SampleDepth(f) f:Seek(34) local bytes = {} for i = 1, 2 do bytes[i] = f:ReadByte(1) end local num = bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0) return num end local function f_SampleRate(f) f:Seek(24) local bytes = {} for i = 1, 4 do bytes[i] = f:ReadByte(1) end local num = bit.lshift(bytes[4], 24) + bit.lshift(bytes[3], 16) + bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0) return num end local function f_Channels(f) f:Seek(22) local bytes = {} for i = 1, 2 do bytes[i] = f:ReadByte(1) end local num = bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0) return num end local function f_Duration(f) return (f:Size() - 44) / (f_SampleDepth(f) / 8 * f_SampleRate(f) * f_Channels(f)) end ixSoundDuration = ixSoundDuration or SoundDuration -- luacheck: globals ixSoundDuration function SoundDuration(str) -- luacheck: globals SoundDuration local path, gamedir = GetSoundPath(str) local f = file.Open(path, "rb", gamedir) if (!f) then return 0 end --Return nil on invalid files local ret if (cache[str]) then ret = cache[str] elseif (f_IsWAV(f)) then ret = f_Duration(f) else ret = ixSoundDuration(str) end f:Close() return ret end end local ADJUST_SOUND = SoundDuration("npc/metropolice/pain1.wav") > 0 and "" or "../../hl2/sound/" --- Emits sounds one after the other from an entity. -- @realm shared -- @entity entity Entity to play sounds from -- @tab sounds Sound paths to play -- @number delay[opt=0] How long to wait before starting to play the sounds -- @number spacing[opt=0.1] How long to wait between playing each sound -- @number volume[opt=75] The sound level of each sound -- @number pitch[opt=100] Pitch percentage of each sound -- @treturn number How long the entire sequence of sounds will take to play function ix.util.EmitQueuedSounds(entity, sounds, delay, spacing, volume, pitch) -- Let there be a delay before any sound is played. delay = delay or 0 spacing = spacing or 0.1 -- Loop through all of the sounds. for _, v in ipairs(sounds) do local postSet, preSet = 0, 0 -- Determine if this sound has special time offsets. if (istable(v)) then postSet, preSet = v[2] or 0, v[3] or 0 v = v[1] end -- Get the length of the sound. local length = SoundDuration(ADJUST_SOUND..v) -- If the sound has a pause before it is played, add it here. delay = delay + preSet -- Have the sound play in the future. timer.Simple(delay, function() -- Check if the entity still exists and play the sound. if (IsValid(entity)) then entity:EmitSound(v, volume, pitch) end end) -- Add the delay for the next sound. delay = delay + length + postSet + spacing end -- Return how long it took for the whole thing. return delay end --- Merges the contents of the second table with the content in the first one. The destination table will be modified. --- If element is table but not metatable object, value's elements will be changed only. -- @realm shared -- @tab destination The table you want the source table to merge with -- @tab source The table you want to merge with the destination table -- @return table function ix.util.MetatableSafeTableMerge(destination, source) for k, v in pairs(source) do if (istable(v) and istable(destination[k]) and getmetatable(v) == nil) then -- don't overwrite one table with another -- instead merge them recurisvely ix.util.MetatableSafeTableMerge(destination[k], v); else destination[ k ] = v; end end return destination; end ix.util.Include("helix/gamemode/core/meta/sh_entity.lua") ix.util.Include("helix/gamemode/core/meta/sh_player.lua") ================================================ FILE: gamemode/init.lua ================================================ -- Include Helix content. resource.AddWorkshop("1267236756") -- Include features from the Sandbox gamemode. DeriveGamemode("sandbox") -- Define a global shared table to store Helix information. ix = ix or {util = {}, meta = {}} -- Send the following files to players. AddCSLuaFile("cl_init.lua") AddCSLuaFile("core/sh_util.lua") AddCSLuaFile("core/sh_data.lua") AddCSLuaFile("shared.lua") -- Include utility functions, data storage functions, and then shared.lua include("core/sh_util.lua") include("core/sh_data.lua") include("shared.lua") -- Resources that are required for players to download are here. resource.AddFile("materials/helix/gui/vignette.png") resource.AddFile("resource/fonts/fontello.ttf") resource.AddFile("sound/helix/intro.mp3") resource.AddFile("sound/helix/ui/press.wav") resource.AddFile("sound/helix/ui/rollover.wav") resource.AddFile("sound/helix/ui/whoosh1.wav") resource.AddFile("sound/helix/ui/whoosh2.wav") resource.AddFile("sound/helix/ui/whoosh3.wav") resource.AddFile("sound/helix/ui/whoosh4.wav") resource.AddFile("sound/helix/ui/whoosh5.wav") resource.AddFile("sound/helix/ui/whoosh6.wav") cvars.AddChangeCallback("sbox_persist", function(name, old, new) -- A timer in case someone tries to rapily change the convar, such as addons with "live typing" or whatever timer.Create("sbox_persist_change_timer", 1, 1, function() hook.Run("PersistenceSave", old) if (new == "") then return end hook.Run("PersistenceLoad", new) end) end, "sbox_persist_load") ================================================ FILE: gamemode/items/ammo/sh_357ammo.txt ================================================ ITEM.name = ".357 Ammo" ITEM.model = "models/items/357ammo.mdl" ITEM.ammo = "357" -- type of the ammo ITEM.ammoAmount = 12 -- amount of the ammo ITEM.description = "A Box that contains %s of .357 Ammo" ITEM.price = 10 ================================================ FILE: gamemode/items/ammo/sh_ar2ammo.txt ================================================ ITEM.name = "AR2 Cartridge" ITEM.model = "models/Items/combine_rifle_cartridge01.mdl" ITEM.ammo = "ar2" -- type of the ammo ITEM.ammoAmount = 30 -- amount of the ammo ITEM.description = "A Cartridge that contains %s of AR2 Ammo" ================================================ FILE: gamemode/items/ammo/sh_crossbowammo.txt ================================================ ITEM.name = "Crossbow Bolts" ITEM.model = "models/Items/CrossbowRounds.mdl" ITEM.ammo = "XBowRounds" -- type of the ammo ITEM.ammoAmount = 5 -- amount of the ammo ITEM.description = "A Bundle of %s Crossbow Bolts" ================================================ FILE: gamemode/items/ammo/sh_pistolammo.txt ================================================ ITEM.name = "Pistol Ammo" ITEM.model = "models/items/357ammo.mdl" ITEM.ammo = "pistol" -- type of the ammo ITEM.ammoAmount = 30 -- amount of the ammo ITEM.description = "A Box that contains %s of Pistol Ammo" ================================================ FILE: gamemode/items/ammo/sh_rocketammo.txt ================================================ ITEM.name = "A Rocket" ITEM.model = "models/weapons/w_missile_closed.mdl" ITEM.ammo = "rpg_round" -- type of the ammo ITEM.ammoAmount = 1 -- amount of the ammo ITEM.width = 2 ITEM.description = "A Package of %s Rockets" ITEM.iconCam = { ang = Angle(-0.70499622821808, 268.25439453125, 0), fov = 12.085652091515, pos = Vector(7, 200, -2) } ================================================ FILE: gamemode/items/ammo/sh_shotgunammo.txt ================================================ ITEM.name = "Shotgun Shells" ITEM.model = "models/Items/BoxBuckshot.mdl" ITEM.ammo = "buckshot" -- type of the ammo ITEM.ammoAmount = 15 -- amount of the ammo ITEM.description = "A Box of %s Shotgun Shells" ================================================ FILE: gamemode/items/ammo/sh_smg1ammo.txt ================================================ ITEM.name = "Sub Machine Gun Ammo" ITEM.model = "models/Items/BoxSRounds.mdl" ITEM.ammo = "smg1" -- type of the ammo ITEM.ammoAmount = 45 -- amount of the ammo ITEM.description = "A Box that contains %s of SMG Ammo" ================================================ FILE: gamemode/items/bags/sh_large.txt ================================================ ITEM.name = "Big Bag" ITEM.description = "A big bag." ITEM.invWidth = 6 ITEM.invHeight = 4 ================================================ FILE: gamemode/items/bags/sh_small.txt ================================================ ITEM.name = "Small Bag" ITEM.description = "A small bag." ================================================ FILE: gamemode/items/base/sh_ammo.lua ================================================ ITEM.name = "Ammo Base" ITEM.model = "models/Items/BoxSRounds.mdl" ITEM.width = 1 ITEM.height = 1 ITEM.ammo = "pistol" -- type of the ammo ITEM.ammoAmount = 30 -- amount of the ammo ITEM.description = "A Box that contains %s of Pistol Ammo" ITEM.category = "Ammunition" ITEM.useSound = "items/ammo_pickup.wav" function ITEM:GetDescription() local rounds = self:GetData("rounds", self.ammoAmount) return Format(self.description, rounds) end if (CLIENT) then function ITEM:PaintOver(item, w, h) draw.SimpleText( item:GetData("rounds", item.ammoAmount), "DermaDefault", w - 5, h - 5, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, 1, color_black ) end end -- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item. ITEM.functions.use = { name = "Load", tip = "useTip", icon = "icon16/add.png", OnRun = function(item) local rounds = item:GetData("rounds", item.ammoAmount) item.player:GiveAmmo(rounds, item.ammo) item.player:EmitSound(item.useSound, 110) return true end, } -- Called after the item is registered into the item tables. function ITEM:OnRegistered() if (ix.ammo) then ix.ammo.Register(self.ammo) end end ================================================ FILE: gamemode/items/base/sh_bags.lua ================================================ if (SERVER) then util.AddNetworkString("ixBagDrop") end ITEM.name = "Bag" ITEM.description = "A bag to hold items." ITEM.model = "models/props_c17/suitcase001a.mdl" ITEM.category = "Storage" ITEM.width = 2 ITEM.height = 2 ITEM.invWidth = 4 ITEM.invHeight = 2 ITEM.isBag = true ITEM.functions.View = { icon = "icon16/briefcase.png", OnClick = function(item) local index = item:GetData("id", "") if (index) then local panel = ix.gui["inv"..index] local inventory = ix.item.inventories[index] local parent = IsValid(ix.gui.menuInventoryContainer) and ix.gui.menuInventoryContainer or ix.gui.openedStorage if (IsValid(panel)) then panel:Remove() end if (inventory and inventory.slots) then panel = vgui.Create("ixInventory", IsValid(parent) and parent or nil) panel:SetInventory(inventory) panel:ShowCloseButton(true) panel:SetTitle(item.GetName and item:GetName() or L(item.name)) if (parent != ix.gui.menuInventoryContainer) then panel:Center() if (parent == ix.gui.openedStorage) then panel:MakePopup() end else panel:MoveToFront() end ix.gui["inv"..index] = panel else ErrorNoHalt("[Helix] Attempt to view an uninitialized inventory '"..index.."'\n") end end return false end, OnCanRun = function(item) return !IsValid(item.entity) and item:GetData("id") and !IsValid(ix.gui["inv" .. item:GetData("id", "")]) end } ITEM.functions.combine = { OnRun = function(item, data) ix.item.instances[data[1]]:Transfer(item:GetData("id"), nil, nil, item.player) return false end, OnCanRun = function(item, data) local index = item:GetData("id", "") if (index) then local inventory = ix.item.inventories[index] if (inventory) then return true end end return false end } if (CLIENT) then function ITEM:PaintOver(item, width, height) local panel = ix.gui["inv" .. item:GetData("id", "")] if (!IsValid(panel)) then return end if (vgui.GetHoveredPanel() == self) then panel:SetHighlighted(true) else panel:SetHighlighted(false) end end end -- Called when a new instance of this item has been made. function ITEM:OnInstanced(invID, x, y) local inventory = ix.item.inventories[invID] ix.inventory.New(inventory and inventory.owner or 0, self.uniqueID, function(inv) local client = inv:GetOwner() inv.vars.isBag = self.uniqueID self:SetData("id", inv:GetID()) if (IsValid(client)) then inv:AddReceiver(client) end end) end function ITEM:GetInventory() local index = self:GetData("id") if (index) then return ix.item.inventories[index] end end ITEM.GetInv = ITEM.GetInventory -- Called when the item first appears for a client. function ITEM:OnSendData() local index = self:GetData("id") if (index) then local inventory = ix.item.inventories[index] if (inventory) then inventory.vars.isBag = self.uniqueID inventory:Sync(self.player) inventory:AddReceiver(self.player) else local owner = self.player:GetCharacter():GetID() ix.inventory.Restore(self:GetData("id"), self.invWidth, self.invHeight, function(inv) inv.vars.isBag = self.uniqueID inv:SetOwner(owner, true) if (!inv.owner) then return end for client, character in ix.util.GetCharacters() do if (character:GetID() == inv.owner) then inv:AddReceiver(client) break end end end) end else ix.inventory.New(self.player:GetCharacter():GetID(), self.uniqueID, function(inv) self:SetData("id", inv:GetID()) end) end end ITEM.postHooks.drop = function(item, result) local index = item:GetData("id") local query = mysql:Update("ix_inventories") query:Update("character_id", 0) query:Where("inventory_id", index) query:Execute() net.Start("ixBagDrop") net.WriteUInt(index, 32) net.Send(item.player) end if (CLIENT) then net.Receive("ixBagDrop", function() local index = net.ReadUInt(32) local panel = ix.gui["inv"..index] if (panel and panel:IsVisible()) then panel:Close() end end) end -- Called before the item is permanently deleted. function ITEM:OnRemoved() local index = self:GetData("id") if (index) then local query = mysql:Delete("ix_items") query:Where("inventory_id", index) query:Execute() query = mysql:Delete("ix_inventories") query:Where("inventory_id", index) query:Execute() end end -- Called when the item should tell whether or not it can be transfered between inventories. function ITEM:CanTransfer(oldInventory, newInventory) local index = self:GetData("id") if (newInventory) then if (newInventory.vars and newInventory.vars.isBag) then return false end local index2 = newInventory:GetID() if (index == index2) then return false end for k, _ in self:GetInventory():Iter() do if (k:GetData("id") == index2) then return false end end end return !newInventory or newInventory:GetID() != oldInventory:GetID() or newInventory.vars.isBag end function ITEM:OnTransferred(curInv, inventory) local bagInventory = self:GetInventory() if (isfunction(curInv.GetOwner)) then local owner = curInv:GetOwner() if (IsValid(owner)) then bagInventory:RemoveReceiver(owner) end end if (isfunction(inventory.GetOwner)) then local owner = inventory:GetOwner() if (IsValid(owner)) then bagInventory:AddReceiver(owner) bagInventory:SetOwner(owner) end else -- it's not in a valid inventory so nobody owns this bag bagInventory:SetOwner(nil) end end -- Called after the item is registered into the item tables. function ITEM:OnRegistered() ix.inventory.Register(self.uniqueID, self.invWidth, self.invHeight, true) end ================================================ FILE: gamemode/items/base/sh_outfit.lua ================================================ ITEM.name = "Outfit" ITEM.description = "A Outfit Base." ITEM.category = "Outfit" ITEM.model = "models/Gibs/HGIBS.mdl" ITEM.width = 1 ITEM.height = 1 ITEM.outfitCategory = "model" ITEM.pacData = {} --[[ -- This will change a player's skin after changing the model. Keep in mind it starts at 0. ITEM.newSkin = 1 -- This will change a certain part of the model. ITEM.replacements = {"group01", "group02"} -- This will change the player's model completely. ITEM.replacements = "models/manhack.mdl" -- This will have multiple replacements. ITEM.replacements = { {"male", "female"}, {"group01", "group02"} } -- This will apply body groups. ITEM.bodyGroups = { ["blade"] = 1, ["bladeblur"] = 1 } ]]-- -- Inventory drawing if (CLIENT) then function ITEM:PaintOver(item, w, h) if (item:GetData("equip")) then surface.SetDrawColor(110, 255, 110, 100) surface.DrawRect(w - 14, h - 14, 8, 8) end end end function ITEM:AddOutfit(client) local character = client:GetCharacter() self:SetData("equip", true) local groups = character:GetData("groups", {}) -- remove original bodygroups if (!table.IsEmpty(groups)) then character:SetData("oldGroups" .. self.outfitCategory, groups) character:SetData("groups", {}) client:ResetBodygroups() end if (isfunction(self.OnGetReplacement)) then character:SetData("oldModel" .. self.outfitCategory, character:GetData("oldModel" .. self.outfitCategory, self.player:GetModel())) character:SetModel(self:OnGetReplacement()) elseif (self.replacement or self.replacements) then character:SetData("oldModel" .. self.outfitCategory, character:GetData("oldModel" .. self.outfitCategory, self.player:GetModel())) if (istable(self.replacements)) then if (#self.replacements == 2 and isstring(self.replacements[1])) then character:SetModel(self.player:GetModel():gsub(self.replacements[1], self.replacements[2])) else for _, v in ipairs(self.replacements) do character:SetModel(self.player:GetModel():gsub(v[1], v[2])) end end else character:SetModel(self.replacement or self.replacements) end end if (self.newSkin) then character:SetData("oldSkin" .. self.outfitCategory, self.player:GetSkin()) self.player:SetSkin(self.newSkin) end -- get outfit saved bodygroups groups = self:GetData("groups", {}) -- restore bodygroups saved to the item if (!table.IsEmpty(groups) and self:ShouldRestoreBodygroups()) then for k, v in pairs(groups) do client:SetBodygroup(k, v) end -- apply default item bodygroups if none are saved elseif (istable(self.bodyGroups)) then for k, v in pairs(self.bodyGroups) do local index = client:FindBodygroupByName(k) if (index > -1) then client:SetBodygroup(index, v) end end end local materials = self:GetData("submaterial", {}) if (!table.IsEmpty(materials) and self:ShouldRestoreSubMaterials()) then for k, v in pairs(materials) do if (!isnumber(k) or !isstring(v)) then continue end client:SetSubMaterial(k - 1, v) end end if (istable(self.attribBoosts)) then for k, v in pairs(self.attribBoosts) do character:AddBoost(self.uniqueID, k, v) end end self:GetOwner():SetupHands() self:OnEquipped() end local function ResetSubMaterials(client) for k, _ in ipairs(client:GetMaterials()) do if (client:GetSubMaterial(k - 1) != "") then client:SetSubMaterial(k - 1) end end end function ITEM:RemoveOutfit(client) local character = client:GetCharacter() self:SetData("equip", false) local materials = {} for k, _ in ipairs(client:GetMaterials()) do if (client:GetSubMaterial(k - 1) != "") then materials[k] = client:GetSubMaterial(k - 1) end end -- save outfit submaterials if (!table.IsEmpty(materials)) then self:SetData("submaterial", materials) end -- remove outfit submaterials ResetSubMaterials(client) local groups = {} for i = 0, (client:GetNumBodyGroups() - 1) do local bodygroup = client:GetBodygroup(i) if (bodygroup > 0) then groups[i] = bodygroup end end -- save outfit bodygroups if (!table.IsEmpty(groups)) then self:SetData("groups", groups) end -- remove outfit bodygroups client:ResetBodygroups() -- restore the original player model if (character:GetData("oldModel" .. self.outfitCategory)) then character:SetModel(character:GetData("oldModel" .. self.outfitCategory)) character:SetData("oldModel" .. self.outfitCategory, nil) end -- restore the original player model skin if (self.newSkin) then if (character:GetData("oldSkin" .. self.outfitCategory)) then client:SetSkin(character:GetData("oldSkin" .. self.outfitCategory)) character:SetData("oldSkin" .. self.outfitCategory, nil) else client:SetSkin(0) end end -- get character original bodygroups groups = character:GetData("oldGroups" .. self.outfitCategory, {}) -- restore original bodygroups if (!table.IsEmpty(groups)) then for k, v in pairs(groups) do client:SetBodygroup(k, v) end character:SetData("groups", character:GetData("oldGroups" .. self.outfitCategory, {})) character:SetData("oldGroups" .. self.outfitCategory, nil) end if (istable(self.attribBoosts)) then for k, _ in pairs(self.attribBoosts) do character:RemoveBoost(self.uniqueID, k) end end for k, _ in pairs(self:GetData("outfitAttachments", {})) do self:RemoveAttachment(k, client) end self:GetOwner():SetupHands() self:OnUnequipped() end -- makes another outfit depend on this outfit in terms of requiring this item to be equipped in order to equip the attachment -- also unequips the attachment if this item is dropped function ITEM:AddAttachment(id) local attachments = self:GetData("outfitAttachments", {}) attachments[id] = true self:SetData("outfitAttachments", attachments) end function ITEM:RemoveAttachment(id, client) local item = ix.item.instances[id] local attachments = self:GetData("outfitAttachments", {}) if (item and attachments[id]) then item:OnDetached(client) end attachments[id] = nil self:SetData("outfitAttachments", attachments) end ITEM:Hook("drop", function(item) if (item:GetData("equip")) then local character = ix.char.loaded[item.owner] local client = character and character:GetPlayer() or item:GetOwner() item.player = client item:RemoveOutfit(item:GetOwner()) end end) ITEM.functions.EquipUn = { -- sorry, for name order. name = "unequip", tip = "unequipTip", icon = "icon16/cross.png", OnRun = function(item) item:RemoveOutfit(item.player) return false end, OnCanRun = function(item) local client = item.player return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and hook.Run("CanPlayerUnequipItem", client, item) != false end } ITEM.functions.Equip = { name = "equip", tip = "equipTip", icon = "icon16/tick.png", OnRun = function(item) local client = item.player local char = client:GetCharacter() for k, _ in char:GetInventory():Iter() do if (k.id != item.id) then local itemTable = ix.item.instances[k.id] if (itemTable.pacData and k.outfitCategory == item.outfitCategory and itemTable:GetData("equip")) then client:NotifyLocalized(item.equippedNotify or "outfitAlreadyEquipped") return false end end end item:AddOutfit(item.player) return false end, OnCanRun = function(item) local client = item.player return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and item:CanEquipOutfit() and hook.Run("CanPlayerEquipItem", client, item) != false end } function ITEM:CanTransfer(oldInventory, newInventory) if (newInventory and self:GetData("equip")) then return false end return true end function ITEM:OnRemoved() if (self.invID != 0 and self:GetData("equip")) then self.player = self:GetOwner() self:RemoveOutfit(self.player) self.player = nil end end function ITEM:OnEquipped() end function ITEM:OnUnequipped() end function ITEM:CanEquipOutfit() return true end function ITEM:ShouldRestoreBodygroups() return true end function ITEM:ShouldRestoreSubMaterials() return true end ================================================ FILE: gamemode/items/base/sh_pacoutfit.lua ================================================ ITEM.name = "PAC Outfit" ITEM.description = "A PAC Outfit Base." ITEM.category = "Outfit" ITEM.model = "models/Gibs/HGIBS.mdl" ITEM.width = 1 ITEM.height = 1 ITEM.outfitCategory = "hat" ITEM.pacData = {} --[[ ITEM.pacData = { [1] = { ["children"] = { [1] = { ["children"] = { }, ["self"] = { ["Angles"] = Angle(12.919322967529, 6.5696062847564e-006, -1.0949343050015e-005), ["Position"] = Vector(-2.099609375, 0.019973754882813, 1.0180969238281), ["UniqueID"] = "4249811628", ["Size"] = 1.25, ["Bone"] = "eyes", ["Model"] = "models/Gibs/HGIBS.mdl", ["ClassName"] = "model", }, }, }, ["self"] = { ["ClassName"] = "group", ["UniqueID"] = "907159817", ["EditorExpand"] = true, }, }, } -- This will change a player's skin after changing the model. Keep in mind it starts at 0. ITEM.newSkin = 1 -- This will change a certain part of the model. ITEM.replacements = {"group01", "group02"} -- This will change the player's model completely. ITEM.replacements = "models/manhack.mdl" -- This will have multiple replacements. ITEM.replacements = { {"male", "female"}, {"group01", "group02"} } -- This will apply body groups. ITEM.bodyGroups = { ["blade"] = 1, ["bladeblur"] = 1 } --]] -- Inventory drawing if (CLIENT) then -- Draw camo if it is available. function ITEM:PaintOver(item, w, h) if (item:GetData("equip")) then surface.SetDrawColor(110, 255, 110, 100) surface.DrawRect(w - 14, h - 14, 8, 8) end end end function ITEM:RemovePart(client) local char = client:GetCharacter() self:SetData("equip", false) client:RemovePart(self.uniqueID) if (self.attribBoosts) then for k, _ in pairs(self.attribBoosts) do char:RemoveBoost(self.uniqueID, k) end end self:OnUnequipped() end -- On item is dropped, Remove a weapon from the player and keep the ammo in the item. ITEM:Hook("drop", function(item) if (item:GetData("equip")) then item:RemovePart(item:GetOwner()) end end) -- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item. ITEM.functions.EquipUn = { -- sorry, for name order. name = "unequip", tip = "unequipTip", icon = "icon16/cross.png", OnRun = function(item) item:RemovePart(item.player) return false end, OnCanRun = function(item) local client = item.player return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and hook.Run("CanPlayerUnequipItem", client, item) != false end } -- On player eqipped the item, Gives a weapon to player and load the ammo data from the item. ITEM.functions.Equip = { name = "equip", tip = "equipTip", icon = "icon16/tick.png", OnRun = function(item) local char = item.player:GetCharacter() for k, _ in char:GetInventory():Iter() do if (k.id != item.id) then local itemTable = ix.item.instances[k.id] if (itemTable.pacData and k.outfitCategory == item.outfitCategory and itemTable:GetData("equip")) then item.player:NotifyLocalized(item.equippedNotify or "outfitAlreadyEquipped") return false end end end item:SetData("equip", true) item.player:AddPart(item.uniqueID, item) if (item.attribBoosts) then for k, v in pairs(item.attribBoosts) do char:AddBoost(item.uniqueID, k, v) end end item:OnEquipped() return false end, OnCanRun = function(item) local client = item.player return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and hook.Run("CanPlayerEquipItem", client, item) != false end } function ITEM:CanTransfer(oldInventory, newInventory) if (newInventory and self:GetData("equip")) then return false end return true end function ITEM:OnRemoved() local inventory = ix.item.inventories[self.invID] local owner = inventory.GetOwner and inventory:GetOwner() if (IsValid(owner) and owner:IsPlayer()) then if (self:GetData("equip")) then self:RemovePart(owner) end end end function ITEM:OnEquipped() end function ITEM:OnUnequipped() end ================================================ FILE: gamemode/items/base/sh_weapons.lua ================================================ ITEM.name = "Weapon" ITEM.description = "A Weapon." ITEM.category = "Weapons" ITEM.model = "models/weapons/w_pistol.mdl" ITEM.class = "weapon_pistol" ITEM.width = 2 ITEM.height = 2 ITEM.isWeapon = true ITEM.isGrenade = false ITEM.weaponCategory = "sidearm" ITEM.useSound = "items/ammo_pickup.wav" -- Inventory drawing if (CLIENT) then function ITEM:PaintOver(item, w, h) if (item:GetData("equip")) then surface.SetDrawColor(110, 255, 110, 100) surface.DrawRect(w - 14, h - 14, 8, 8) end end function ITEM:PopulateTooltip(tooltip) if (self:GetData("equip")) then local name = tooltip:GetRow("name") name:SetBackgroundColor(derma.GetColor("Success", tooltip)) end end end -- On item is dropped, Remove a weapon from the player and keep the ammo in the item. ITEM:Hook("drop", function(item) local inventory = ix.item.inventories[item.invID] if (!inventory) then return end -- the item could have been dropped by someone else (i.e someone searching this player), so we find the real owner local owner for client, character in ix.util.GetCharacters() do if (character:GetID() == inventory.owner) then owner = client break end end if (!IsValid(owner)) then return end if (item:GetData("equip")) then item:SetData("equip", nil) owner.carryWeapons = owner.carryWeapons or {} local weapon = owner.carryWeapons[item.weaponCategory] if (!IsValid(weapon)) then weapon = owner:GetWeapon(item.class) end if (IsValid(weapon)) then item:SetData("ammo", weapon:Clip1()) owner:StripWeapon(item.class) owner.carryWeapons[item.weaponCategory] = nil owner:EmitSound(item.useSound, 80) end item:RemovePAC(owner) end end) -- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item. ITEM.functions.EquipUn = { -- sorry, for name order. name = "unequip", tip = "unequipTip", icon = "icon16/cross.png", OnRun = function(item) item:Unequip(item.player, true) return false end, OnCanRun = function(item) local client = item.player return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and hook.Run("CanPlayerUnequipItem", client, item) != false end } -- On player eqipped the item, Gives a weapon to player and load the ammo data from the item. ITEM.functions.Equip = { name = "equip", tip = "equipTip", icon = "icon16/tick.png", OnRun = function(item) item:Equip(item.player) return false end, OnCanRun = function(item) local client = item.player return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and hook.Run("CanPlayerEquipItem", client, item) != false end } function ITEM:WearPAC(client) if (ix.pac and self.pacData) then client:AddPart(self.uniqueID, self) end end function ITEM:RemovePAC(client) if (ix.pac and self.pacData) then client:RemovePart(self.uniqueID) end end function ITEM:Equip(client, bNoSelect, bNoSound) client.carryWeapons = client.carryWeapons or {} for k, _ in client:GetCharacter():GetInventory():Iter() do if (k.id != self.id) then local itemTable = ix.item.instances[k.id] if (!itemTable) then client:NotifyLocalized("tellAdmin", "wid!xt") return false else if (itemTable.isWeapon and client.carryWeapons[self.weaponCategory] and itemTable:GetData("equip")) then client:NotifyLocalized("weaponSlotFilled", self.weaponCategory) return false end end end end if (client:HasWeapon(self.class)) then client:StripWeapon(self.class) end local weapon = client:Give(self.class, !self.isGrenade) if (IsValid(weapon)) then local ammoType = weapon:GetPrimaryAmmoType() client.carryWeapons[self.weaponCategory] = weapon if (!bNoSelect) then client:SelectWeapon(weapon:GetClass()) end if (!bNoSound) then client:EmitSound(self.useSound, 80) end -- Remove default given ammo. if (client:GetAmmoCount(ammoType) == weapon:Clip1() and self:GetData("ammo", 0) == 0) then client:RemoveAmmo(weapon:Clip1(), ammoType) end -- assume that a weapon with -1 clip1 and clip2 would be a throwable (i.e hl2 grenade) -- TODO: figure out if this interferes with any other weapons if (weapon:GetMaxClip1() == -1 and weapon:GetMaxClip2() == -1 and client:GetAmmoCount(ammoType) == 0) then client:SetAmmo(1, ammoType) end self:SetData("equip", true) if (self.isGrenade) then weapon:SetClip1(1) client:SetAmmo(0, ammoType) else weapon:SetClip1(self:GetData("ammo", 0)) end weapon.ixItem = self if (self.OnEquipWeapon) then self:OnEquipWeapon(client, weapon) end else print(Format("[Helix] Cannot equip weapon - %s does not exist!", self.class)) end end function ITEM:Unequip(client, bPlaySound, bRemoveItem) client.carryWeapons = client.carryWeapons or {} local weapon = client.carryWeapons[self.weaponCategory] if (!IsValid(weapon)) then weapon = client:GetWeapon(self.class) end if (IsValid(weapon)) then weapon.ixItem = nil self:SetData("ammo", weapon:Clip1()) client:StripWeapon(self.class) else print(Format("[Helix] Cannot unequip weapon - %s does not exist!", self.class)) end if (bPlaySound) then client:EmitSound(self.useSound, 80) end client.carryWeapons[self.weaponCategory] = nil self:SetData("equip", nil) self:RemovePAC(client) if (self.OnUnequipWeapon) then self:OnUnequipWeapon(client, weapon) end if (bRemoveItem) then self:Remove() end end function ITEM:CanTransfer(oldInventory, newInventory) if (newInventory and self:GetData("equip")) then local owner = self:GetOwner() if (IsValid(owner)) then owner:NotifyLocalized("equippedWeapon") end return false end return true end function ITEM:OnLoadout() if (self:GetData("equip")) then local client = self.player client.carryWeapons = client.carryWeapons or {} local weapon = client:Give(self.class, true) if (IsValid(weapon)) then client:RemoveAmmo(weapon:Clip1(), weapon:GetPrimaryAmmoType()) client.carryWeapons[self.weaponCategory] = weapon weapon.ixItem = self weapon:SetClip1(self:GetData("ammo", 0)) if (self.OnEquipWeapon) then self:OnEquipWeapon(client, weapon) end else print(Format("[Helix] Cannot give weapon - %s does not exist!", self.class)) end end end function ITEM:OnSave() local weapon = self.player:GetWeapon(self.class) if (IsValid(weapon) and weapon.ixItem == self and self:GetData("equip")) then self:SetData("ammo", weapon:Clip1()) end end function ITEM:OnRemoved() local inventory = ix.item.inventories[self.invID] local owner = inventory.GetOwner and inventory:GetOwner() if (IsValid(owner) and owner:IsPlayer()) then local weapon = owner:GetWeapon(self.class) if (IsValid(weapon)) then weapon:Remove() end self:RemovePAC(owner) end end hook.Add("PlayerDeath", "ixStripClip", function(client) client.carryWeapons = {} for k, _ in client:GetCharacter():GetInventory():Iter() do if (k.isWeapon and k:GetData("equip")) then k:SetData("ammo", nil) k:SetData("equip", nil) if (k.pacData) then k:RemovePAC(client) end end end end) hook.Add("EntityRemoved", "ixRemoveGrenade", function(entity) -- hack to remove hl2 grenades after they've all been thrown if (entity:GetClass() == "weapon_frag") then local client = entity:GetOwner() if (IsValid(client) and client:IsPlayer() and client:GetCharacter()) then local ammoName = game.GetAmmoName(entity:GetPrimaryAmmoType()) if (isstring(ammoName) and ammoName:lower() == "grenade" and client:GetAmmoCount(ammoName) < 1 and entity.ixItem and entity.ixItem.Unequip) then entity.ixItem:Unequip(client, false, true) end end end end) ================================================ FILE: gamemode/items/pacoutfit/sh_skullmask.txt ================================================ ITEM.name = "Skull Mask" ITEM.description = "It's a skull mask." ITEM.model = "models/Gibs/HGIBS.mdl" ITEM.width = 1 ITEM.height = 1 ITEM.outfitCategory = "hat" ITEM.pacData = { [1] = { ["children"] = { [1] = { ["children"] = { }, ["self"] = { ["Angles"] = Angle(12.919322967529, 6.5696062847564e-006, -1.0949343050015e-005), ["Position"] = Vector(-2.099609375, 0.019973754882813, 1.3180969238281), ["UniqueID"] = "4249811628", ["Size"] = 1.25, ["Bone"] = "eyes", ["Model"] = "models/Gibs/HGIBS.mdl", ["ClassName"] = "model", }, }, }, ["self"] = { ["ClassName"] = "group", ["UniqueID"] = "907159817", ["EditorExpand"] = true, }, }, } ================================================ FILE: gamemode/items/sh_defaultitem.txt ================================================ ITEM.name = "Test Item" ITEM.description = "A test item!" ITEM.model = "models/props_c17/oildrum001.mdl" ================================================ FILE: gamemode/items/weapons/sh_357.txt ================================================ ITEM.name = "357" ITEM.description = "A sidearm utilising .357 Caliber ammunition." ITEM.model = "models/weapons/w_357.mdl" ITEM.class = "weapon_357" ITEM.weaponCategory = "sidearm" ITEM.width = 2 ITEM.height = 1 ITEM.iconCam = { ang = Angle(-17.581502914429, 250.7974395752, 0), fov = 5.412494001838, pos = Vector(57.109928131104, 181.7945098877, -60.738327026367) } ================================================ FILE: gamemode/items/weapons/sh_ar2.txt ================================================ ITEM.name = "AR2" ITEM.description = "A Weapon." ITEM.model = "models/weapons/w_IRifle.mdl" ITEM.class = "weapon_ar2" ITEM.weaponCategory = "primary" ITEM.width = 4 ITEM.height = 2 ITEM.iconCam = { ang = Angle(-0.70499622821808, 268.25439453125, 0), fov = 12.085652091515, pos = Vector(0, 200, 0) } ================================================ FILE: gamemode/items/weapons/sh_crowbar.txt ================================================ ITEM.name = "Crowbar" ITEM.description = "A slightly rusty looking crowbar." ITEM.model = "models/weapons/w_crowbar.mdl" ITEM.class = "weapon_crowbar" ITEM.weaponCategory = "melee" ITEM.width = 2 ITEM.height = 1 ITEM.iconCam = { ang = Angle(-0.23955784738064, 270.44906616211, 0), fov = 10.780103254469, pos = Vector(0, 200, 0) } ================================================ FILE: gamemode/items/weapons/sh_pistol.txt ================================================ ITEM.name = "9MM Pistol" ITEM.description = "A sidearm utilising 9mm Ammunition." ITEM.model = "models/weapons/w_pistol.mdl" ITEM.class = "weapon_pistol" ITEM.weaponCategory = "sidearm" ITEM.width = 2 ITEM.height = 1 ITEM.iconCam = { ang = Angle(0.33879372477531, 270.15808105469, 0), fov = 5.0470897275697, pos = Vector(0, 200, -1) } ================================================ FILE: gamemode/items/weapons/sh_smg1.txt ================================================ ITEM.name = "Sub Machine Gun" ITEM.description = "A Weapon." ITEM.model = "models/weapons/w_smg1.mdl" ITEM.class = "weapon_smg1" ITEM.weaponCategory = "primary" ITEM.width = 3 ITEM.height = 2 ITEM.iconCam = { ang = Angle(-0.020070368424058, 270.40155029297, 0), fov = 7.2253324508038, pos = Vector(0, 200, -1) } ================================================ FILE: gamemode/languages/sh_dutch.lua ================================================ -- DUTCH TRANSLATION - http://steamcommunity.com/profiles/76561198084653538/ NAME = "Nederlands" LANGUAGE = { loading = "Laden", dbError = "Database verbinding gefaald", unknown = "Onbekend", noDesc = "Geen beschrijving beschikbaar", create = "Creëer", createTip = "Maak een nieuw karakter aan om mee te spelen.", load = "Laad", loadTip = "Kies een eerder gemaakte karakter om mee te spelen.", leave = "Verlaat", leaveTip = "Verlaat de huidige server.", ["return"] = "Terug", returnTip = "Ga terug naar het vorige menu.", name = "Naam", description = "Beschrijving", model = "Model", attributes = "Attributen", charCreateTip = "Vul de velden hieronder in en druk op 'Voltooi' om je karakter te maken.", invalid = "Je hebt een ongeldige %s voorzien", descMinLen = "Je beschrijving moet ten minste %d tekens zijn.", player = "Speler", finish = "Voltooi", finishTip = "Voltooi het maken van jouw karakter.", needModel = "Je moet een geldig 'Model' kiezen.", creating = "Je karakter wordt gemaakt...", unknownError = "Er is een onbekende fout opgetreden", delConfirm = "Weet je zeker dat je %s wilt verwijderen?", no = "Nee", yes = "Ja", itemInfo = "Naam: %s\nBeschrijving: %s", cloud_no_repo = "Deze opslaagplaats is niet geldig.", cloud_no_plugin = "Deze plugin is niet geldig.", inv = "Inventaris", plugins = "Plugins", author = "Auteur", version = "Versie", characters = "Karakters", business = "Bedrijf", settings = "Instellingen", config = "Config", chat = "Chat", appearance = "Uiterlijk", misc = "Gemengd", oocDelay = "Je moet %s seconde(n) wachten voordat je weer in OOC kan praten.", loocDelay = "Je moet %s seconde(n) wachten voordat je weer in LOOC kan praten.", usingChar = "Je bent dit karakter al aan het gebruiken.", itemNoExist = "Sorry, het item dat je hebt opgevraagd bestaat niet.", cmdNoExist = "Sorry, dat commando bestaat niet.", plyNoExist = "Sorry, Er is geen speler gevonden met die naam.", cfgSet = "%s heeft \"%s\" gezet tot %s.", drop = "Vallen", dropTip = "Dit laat deze item(s) vallen uit je inventaris.", take = "Nemen", takeTip = "Dit laat deze item(s) oppakken en het in je inventaris doen.", dTitle = "Unowned Door", dTitleOwned = "Purchased Door", dIsNotOwnable = "Deze deur kan niet gekocht worden.", dIsOwnable = "Je kan deze deur kopen door op 'F2' te drukken.", dMadeUnownable = "Je hebt deze deur niet verkoopbaar gemaakt.", dMadeOwnable = "Je hebt deze deur verkoopbaar gemaakt.", dNotAllowedToOwn = "Je mag deze deur niet bezitten.", dSetDisabled = "Je hebt deze deur uitgeschakeld.", dSetNotDisabled = "Je hebt deze deur ingeschakeld.", dSetParentDoor = "Je hebt deze deur als je 'Parent' deur gezet. ", dCanNotSetAsChild = "Je kan deze 'Parent' deur niet als een 'Child' deur zetten.", dAddChildDoor = "Je hebt deze deur als een 'Child' toegevoegd.", dRemoveChildren = "Je hebt alle 'Childs' van deze deur verwijderd.", dRemoveChildDoor = "Je hebt de 'Child' status van deze deur verwijderd.", dNoParentDoor = "Je hebt nog geen 'Parent' deur ingeschakeld.", dOwnedBy = "%s is de eigenaar van deze deur.", dConfigName = "Deuren", dSetFaction = "Deze deur behoort nu toe aan de %s 'faction'.", dRemoveFaction = "Deze deur behoort niet meer toe aan een 'faction'.", dNotValid = "Je kijkt niet naar een geldige deur.", canNotAfford = "Je kunt het je niet veroorloven om dit te kopen.", dPurchased = "Je hebt deze deur gekocht voor %s.", dSold = "Je hebt deze deur verkocht voor %s.", notOwner = "Je bent niet de eigenaar van dit.", invalidArg = "Je hebt een ongeldige waarde voor argument #%s.", flagGive = "%s heeft %s '%s' flags gegeven.", flagTake = "%s heeft '%s' flags van %s genomen.", flagNoMatch = "Je moet (de) \"%s\" Flag(s) hebben om dit te doen", textAdded = "Je hebt een tekst toegevoegd.", textRemoved = "Je hebt de %s tekst(en) verwijderd.", moneyTaken = "Je hebt %s gevonden.", businessPurchase = "Je hebt %s gekocht voor %s.", businessSell = "Je verkocht %s voor %s.", cChangeModel = "%s veranderde %s's model naar %s.", cChangeName = "%s veranderde %s's naam naar %s.", playerCharBelonging = "Dit object behoort aan je andere karakter toe.", invalidFaction = "Je hebt een ongeldige 'faction' voorzien", spawnAdd = "Je hebt een spawn toegevoegd voor de %s 'faction'.", spawnDeleted = "Je hebt (een) %s spawn punt(en) verwijderd.", someone = "Iemand", rgnLookingAt = "Toestaan dat de persoon waar je naar kijkt je herkent.", rgnWhisper = "Toestaan dat degene(n) in het fluister gebied je herkent.", rgnTalk = "Toestaan dat de personen in het praat gebied je herkennen.", rgnYell = "Toestaan dat de personen in het schreeuw gebied je herkennen.", icFormat = "%s zegt \"%s\"", rollFormat = "%s heeft %s uitgerold.", wFormat = "%s fluistert \"%s\"", yFormat = "%s schreeuwt \"%s\"", sbOptions = "Klik om opties over %s te zien.", spawnAdded = "Je hebt een spawn toegevoegd voor %s.", whitelist = "%s heeft %s gewhitelist voor de %s 'faction'.", unwhitelist = "%s heeft %s geblacklist van de %s 'faction'.", gettingUp = "Je staat nu op...", wakingUp = "Je krijgt je bewustzijn terug...", Weapons = "Wapens", checkout = "Ga naar checkout (%s)", purchase = "Koop", purchasing = "Aan het kopen...", success = "Succes", buyFailed = "De koop is gefaald!", buyGood = "Succesvol gekocht!", shipment = "Lading", shipmentDesc = "Deze lading behoort toe aan %s.", class = "Class", classes = "Classes", illegalAccess = "Illegale toegang", becomeClassFail = "Het is niet gelukt om %s te worden.", becomeClass = "Je bent %s geworden.", attributeSet = "You set %s's %s to %s.", attributeUpdate = "You added %s's %s by %s.", noFit = "Deze item(s) passen niet in je inventaris", help = "Hulp", commands = "Commando's", helpDefault = "Selecteer een categorie", doorSettings = "Deur Instellingen", sell = "Verkoop", access = "Toegang", locking = "Bezig deze 'Entity' opslot te zetten ...", unlocking = "Bezig deze 'Entity' van het slot te zetten ...", modelNoSeq = "Je model ondersteunt deze 'Act' niet.", notNow = "Je kan dat nu niet doen.", faceWall = "Je moet de muur aankijken om dit te doen.", faceWallBack = "Je moet met je rug tegen de muur staan om dit te doen", descChanged = "Je hebt je karakter's beschrijving veranderd.", charMoney = "Je hebt momenteel %s.", charFaction = "Je bent een lid van de %s 'faction'.", charClass = "Je bent %s van de 'faction'.", noOwner = "De eigenaar is niet geldig.", notAllowed = "Dit is niet toegestaan.", invalidIndex = "De Item Index is Ongeldig.", invalidItem = "De Item Object is Ongeldig.", invalidInventory = "Het inventaris object is ongeldig.", home = "Home", charKick = "%s kickde karakter %s.", charBan = "%s heeft karakter %s verbannen.", charBanned = "Deze karakter is verbannen.", setMoney = "Je hebt %s's geld tot %s gezet." } ================================================ FILE: gamemode/languages/sh_english.lua ================================================ NAME = "English" LANGUAGE = { helix = "Helix", introTextOne = "fist industries presents", introTextTwo = "in collaboration with %s", introContinue = "press space to continue", helpIdle = "Select a category", helpCommands = "Command parameters with are required, while [brackets] are optional.", helpFlags = "Flags with a green background are accessible by this character.", creditSpecial = "Special Thanks", creditLeadDeveloper = "Lead Developer", creditUIDesigner = "UI Designer", creditManager = "Project Manager", creditTester = "Lead Tester", chatTyping = "Typing...", chatTalking = "Talking...", chatYelling = "Yelling...", chatWhispering = "Whispering...", chatPerforming = "Performing...", chatNewTab = "New tab...", chatReset = "Reset position", chatResetTabs = "Reset tabs", chatCustomize = "Customize...", chatCloseTab = "Close Tab", chatTabName = "Tab Name", chatNewTabTitle = "New Tab", chatAllowedClasses = "Allowed Chat Classes", chatTabExists = "A chat tab with that name already exists!", chatMarkRead = "Mark all as read", community = "Community", checkAll = "Check All", uncheckAll = "Uncheck All", color = "Color", type = "Type", display = "Display", loading = "Loading", dbError = "Database connection failed", unknown = "Unknown", noDesc = "No description available", create = "Create", update = "Update", load = "Load character", loadTitle = "Load a Character", leave = "Leave server", leaveTip = "Leave the current server.", ["return"] = "Return", returnTip = "Return to the previous menu.", proceed = "Proceed", faction = "Faction", skills = "Skills", choose = "Choose", chooseFaction = "Choose a Faction", chooseDescription = "Define your Narrative", chooseSkills = "Hone your Skills", name = "Name", description = "Description", model = "Model", attributes = "Attributes", attribPointsLeft = "Points left", charInfo = "Character Information", charCreated = "You have successfully created your character.", charCreateTip = "Fill in the fields below and press 'Finish' to create your character.", invalid = "You have provided an invalid %s!", nameMinLen = "Your name must be at least %d characters!", nameMaxLen = "Your name must not be more than %d characters!", descMinLen = "Your description must be at least %d characters!", maxCharacters = "You cannot create any more characters!", player = "Player", finish = "Finish", finishTip = "Finish creating the character.", needModel = "You need to choose a valid model!", creating = "Your character is being created...", unknownError = "An unknown error has occured!", areYouSure = "Are you sure?", delete = "Delete", deleteConfirm = "This character will be irrevocably removed!", deleteComplete = "%s has been deleted.", no = "No", yes = "Yes", close = "Close", save = "Save", itemInfo = "Name: %s\nDescription: %s", itemCreated = "Item(s) successfully created.", cloud_no_repo = "The repository provided is not valid!", cloud_no_plugin = "The plugin provided is not valid!", inv = "Inventory", plugins = "Plugins", pluginLoaded = "%s has enabled the \"%s\" plugin to load next map change.", pluginUnloaded = "%s has disabled the \"%s\" plugin from loading next map change.", loadedPlugins = "Loaded Plugins", unloadedPlugins = "Unloaded Plugins", on = "On", off = "Off", author = "Author", version = "Version", characters = "Characters", business = "Business", settings = "Settings", config = "Config", chat = "Chat", appearance = "Appearance", misc = "Miscellaneous", oocDelay = "You must wait %s more seconds before using OOC again!", loocDelay = "You must wait %s more seconds before using LOOC again!", usingChar = "You are already using this character.", notAllowed = "You are not allowed to do this!", itemNoExist = "The item that you requested does not exist!", cmdNoExist = "That command does not exist!", charNoExist = "A matching character could not be found!", plyNoExist = "A matching player could not be found!", cfgSet = "%s has set \"%s\" to %s.", drop = "Drop", dropTip = "Drops this item from your inventory.", take = "Take", takeTip = "Take this item and place it in your inventory.", dTitle = "Unowned Door", dTitleOwned = "Purchased Door", dIsNotOwnable = "This door is unownable.", dIsOwnable = "You can purchase this door by pressing F2.", dMadeUnownable = "You have made this door unownable.", dMadeOwnable = "You have made this door ownable.", dNotAllowedToOwn = "You are not allowed to own this door!", dSetDisabled = "You have made this door disabled.", dSetNotDisabled = "You have made this door no longer disabled.", dSetHidden = "You have made this door hidden.", dSetNotHidden = "You have made this door no longer hidden.", dSetParentDoor = "You have set this door as your parent door.", dCanNotSetAsChild = "You can not set the parent door as a child!", dAddChildDoor = "You have added a this door as a child.", dRemoveChildren = "You have removed all of the children for this door.", dRemoveChildDoor = "You have removed this door from being a child.", dNoParentDoor = "You do not have a parent door set.", dOwnedBy = "This door is owned by %s.", dConfigName = "Doors", dSetFaction = "This door now belongs to the %s faction.", dRemoveFaction = "This door no longer belongs to any faction.", dNotValid = "You are not looking at a valid door!", canNotAfford = "You can not afford to purchase this!", dPurchased = "You have purchased this door for %s.", dSold = "You have sold this door for %s.", notOwner = "You are not the owner of this!", invalidArg = "You have provided an invalid value for argument #%s!", invalidFaction = "The faction you provided could not be found!", flagGive = "%s has given %s '%s' flags.", flagGiveTitle = "Give Flags", flagTake = "%s has taken '%s' flags from %s.", flagTakeTitle = "Take Flags", flagNoMatch = "You must have \"%s\" flag(s) to do this action!", panelAdded = "You have added a panel.", panelRemoved = "You have removed %d panel(s).", textAdded = "You have added a text.", textRemoved = "You have removed %s text(s).", moneyTaken = "You were given %s.", moneyGiven = "You have given %s.", insufficientMoney = "You do not have enough to do this!", belowMinMoneyDrop = "You cannot drop less than %s.", businessPurchase = "You purchased %s for %s.", businessSell = "You sold %s for %s.", businessTooFast = "Please wait before purchasing another item!", cChangeModel = "%s changed %s's model to %s.", cChangeName = "%s changed %s's name to %s.", cChangeSkin = "%s changed %s's skin to %s.", cChangeGroups = "%s changed %s's \"%s\" bodygroup to %s.", cChangeFaction = "%s has transferred %s to the %s faction.", playerCharBelonging = "This object is your other character's belonging!", spawnAdd = "You have added a spawn for the %s.", spawnDeleted = "You have removed %s spawn point(s).", someone = "Someone", rgnLookingAt = "Allow the person you are looking at to recognize you.", rgnWhisper = "Allow those in a whispering range to recognize you.", rgnTalk = "Allow those in a talking range to recognize you.", rgnYell = "Allow those in a yelling range to recognize you.", icFormat = "%s says \"%s\"", rollFormat = "%s has rolled %s out of %s.", wFormat = "%s whispers \"%s\"", yFormat = "%s yells \"%s\"", sbOptions = "Click to see options for %s.", spawnAdded = "You added spawn for %s.", whitelist = "%s has whitelisted %s for the %s faction.", unwhitelist = "%s has unwhitelisted %s from the %s faction.", noWhitelist = "You do not have the whitelist for this character!", charNotWhitelisted = "%s is not whitelisted for the %s faction.", gettingUp = "You are now getting up...", wakingUp = "You are regaining consciousness...", Weapons = "Weapons", checkout = "Go to Checkout (%s)", purchase = "Purchase", purchasing = "Purchasing...", success = "Success", buyFailed = "Purchase failed!", buyGood = "Purchase successful!", shipment = "Shipment", shipmentDesc = "This shipment belongs to %s.", class = "Class", classes = "Classes", illegalAccess = "Illegal Access.", becomeClassFail = "You cannot become a %s!", becomeClass = "You have become a %s.", setClass = "You have set %s's class to %s.", attributeSet = "You set %s's %s to %s.", attributeNotFound = "You have specified an invalid attribute!", attributeUpdate = "You added %s's %s by %s.", noFit = "You do not have enough space for this item!", itemOwned = "You cannot interact with an item that you own on a different character!", help = "Help", commands = "Commands", doorSettings = "Door Settings", sell = "Sell", access = "Access", locking = "Locking this entity...", unlocking = "Unlocking this entity...", modelNoSeq = "Your model does not support this act!", notNow = "You are not allowed to do this right now!", faceWall = "You must be facing the wall to do this!", faceWallBack = "Your back must be facing the wall to do this!", descChanged = "You have changed your character's description.", noOwner = "The owner is invalid!", invalidItem = "You have specified an invalid item!", invalidInventory = "You have specified an invalid inventory!", home = "Home", charKick = "%s kicked char %s.", charBan = "%s banned the character %s.", charBanned = "This character is banned.", charBannedTemp = "This character is temporarily banned.", playerConnected = "%s has connected to the server.", playerDisconnected = "%s has disconnected from the server.", setMoney = "You have set %s's money to %s.", itemPriceInfo = "You can purchase this item for %s.\nYou can sell this item for %s", free = "Free", vendorNoSellItems = "There are no items to sell.", vendorNoBuyItems = "There are no items to purchase.", vendorSettings = "Vendor Settings", vendorUseMoney = "Vendor should use money?", vendorNoBubble = "Hide vendor bubble?", category = "Category", mode = "Mode", price = "Price", stock = "Stock", none = "None", vendorBoth = "Buy and Sell", vendorBuy = "Buy Only", vendorSell = "Sell Only", maxStock = "Max Stock", vendorFaction = "Faction Editor", buy = "Purchase", vendorWelcome = "Welcome to my store, what can I get you today?", vendorBye = "Come again soon!", charSearching = "You are already searching another character!", charUnBan = "%s has unbanned the character %s.", charNotBanned = "This character is not banned!", quickSettings = "Quick Settings", vmSet = "You have set your voicemail.", vmRem = "You have removed your voicemail.", noPerm = "You are not allowed to do this!", youreDead = "You are Dead", injMajor = "Seems critically injured", injLittle = "Seems injured", chgName = "Change Name", chgNameDesc = "Enter the character's new name below.", weaponSlotFilled = "You cannot use another %s weapon!", equippedBag = "You cannot move a bag that has an equipped item!", equippedWeapon = "You cannot move a weapon that is currently equipped!", nestedBags = "You cannot put an inventory inside of a storage inventory!", outfitAlreadyEquipped = "You're already wearing this kind of outfit!", useTip = "Uses the item.", equip = "Equip", equipTip = "Equips the item.", unequip = "Unequip", unequipTip = "Unequips the item.", consumables = "Consumables", plyNotValid = "You are not looking at a valid player!", restricted = "You have been restrained.", salary = "You have received %s from your salary.", noRecog = "You do not recognize this person.", curTime = "The current time is %s.", vendorEditor = "Vendor Editor", edit = "Edit", disable = "Disable", vendorPriceReq = "Enter the new price for this item.", vendorEditCurStock = "Edit Current Stock", vendorStockReq = "Enter the Maximum amount of Stock the item should have.", vendorStockCurReq = "Enter how many items are available for purchase from the maximum stock.", you = "You", vendorSellScale = "Sell price scale", vendorNoTrade = "You are not able to trade with this vendor!", vendorNoMoney = "This vendor can not afford that item!", vendorNoStock = "This vendor does not have that item in stock!", vendorMaxStock = "This vendor has full stock of that item!", contentTitle = "Helix Content Missing", contentWarning = "You do not have the Helix content mounted. This may result in certain features missing.\nWould you like to open the Workshop page for the Helix content?", flags = "Flags", mapRestarting = "The map will restart in %d seconds!", chooseTip = "Choose this character to play with.", deleteTip = "Delete this character.", storageInUse = "Someone else is using this!", storageSearching = "Searching...", container = "Container", containerPassword = "You have set this container's password to %s.", containerPasswordRemove = "You have removed this container's password.", containerPasswordWrite = "Enter the password.", containerName = "You have set this container's name to %s.", containerNameWrite = "Enter the name.", containerNameRemove = "You have removed this container's name.", containerInvalid = "You need to be looking at a container to do this!", wrongPassword = "You have entered an incorrect password!", passwordAttemptLimit = "You have made too many incorrect password attempts!", respawning = "Respawning...", tellAdmin = "Inform a staff member of this error: %s", syntax = "Syntax: %s", mapAdd = "You have added a map scene.", mapDel = "You have removed %d map scene(s).", mapRepeat = "Now add the secondary point.", scoreboard = "Scoreboard", ping = "Ping: %d", viewProfile = "View Steam Profile", copySteamID = "Copy Steam ID", money = "Money", moneyLeft = "Your Money: ", currentMoney = "Money Left: ", invalidClass = "That is not a valid class!", invalidClassFaction = "That is not a valid class for that faction!", miscellaneous = "Miscellaneous", general = "General", observer = "Observer", performance = "Performance", thirdperson = "Third Person", date = "Date", interaction = "Interaction", server = "Server", resetDefault = "Reset to default", resetDefaultDescription = "This will reset \"%s\" to its default value of \"%s\".", optOpenBags = "Open bags with inventory", optdOpenBags = "Automatically views all the bags in your inventory when the menu is opened.", optShowIntro = "Show intro on join", optdShowIntro = "Shows the Helix introduction the next time you join. This is always disabled after you've watched it.", optCheapBlur = "Disable blurring", optdCheapBlur = "Replaces UI blurring with simple dimming.", optObserverTeleportBack = "Return to previous location", optdObserverTeleportBack = "Returns you to the location that you entered observer mode.", optObserverESP = "Show admin ESP", optdObserverESP = "Shows the names and locations of each player in the server.", opt24hourTime = "Use 24-hour time", optd24hourTime = "Show timestamps in 24-hour time, rather than 12-hour time (AM/PM).", optChatNotices = "Show notices in chat", optdChatNotices = "Puts all notices that appear in the top-right into the chat instead.", optChatTimestamps = "Show timestamps in chat", optdChatTimestamps = "Prepends the time to each message in the chatbox.", optAlwaysShowBars = "Always show info bars", optdAlwaysShowBars = "Draws the information bars in the top-left, regardless if it should be hidden or not.", optAltLower = "Hide hands when lowered", -- @todo remove me optdAltLower = "Hides your hands when they are by your side.", -- @todo remove me optThirdpersonEnabled = "Enable third person", optdThirdpersonEnabled = "Moves the camera behind you. This can also be enabled with the \"ix_togglethirdperson\" console command.", optThirdpersonClassic = "Enable classic third person camera", optdThirdpersonClassic = "Moves your character's view with your mouse.", optThirdpersonVertical = "Camera height", optdThirdpersonVertical = "How high up the camera should be.", optThirdpersonHorizontal = "Camera offset", optdThirdpersonHorizontal = "How far left or right the camera should be.", optThirdpersonDistance = "Camera distance", optdThirdpersonDistance = "How far away the camera should be.", optThirdpersonCrouchOffset = "Camera crouch height", optdThirdpersonCrouchOffset = "How high up the camera should be while crouched.", optDisableAnimations = "Disable animations", optdDisableAnimations = "Stops animations from playing to provide instant transitions.", optAnimationScale = "Animation scale", optdAnimationScale = "How much faster or slower the animations should play.", optLanguage = "Language", optdLanguage = "The language shown in the Helix UI.", optMinimalTooltips = "Minimal HUD tooltips", optdMinimalTooltips = "Changes the HUD tooltip style to take up less space.", optNoticeDuration = "Notice duration", optdNoticeDuration = "How long to show notices for (in seconds).", optNoticeMax = "Maximum notices", optdNoticeMax = "The amount of notices shown before excess previous ones are removed.", optChatFontScale = "Chat font scale", optdChatFontScale = "How much bigger or smaller the chat font should be.", optChatOutline = "Outline chat text", optdChatOutline = "Draws an outline around the chat text, rather than a drop shadow. Enable this if you are having trouble reading text.", optEscCloseMenu = "Escape returns to game", optdEscCloseMenu = "Whether the escape menu should close itself when you're using it to close the in-game menu.", cmdRoll = "Rolls a number between 0 and the specified number.", cmdPM = "Sends a private message to someone.", cmdReply = "Sends a private message to the last person you received a message from.", cmdSetVoicemail = "Sets or removes the auto-reply message when someone sends you a private message.", cmdCharGiveFlag = "Gives the specified flag(s) to someone.", cmdCharTakeFlag = "Removes the specified flag(s) from someone if they have it.", cmdToggleRaise = "Raises or lowers the weapon you're holding.", cmdCharSetModel = "Sets a person's character model.", cmdCharSetSkin = "Sets the skin for a person's character model.", cmdCharSetBodygroup = "Sets the bodygroup for a person's character model.", cmdCharSetAttribute = "Sets the level of the specified attribute for someone.", cmdCharAddAttribute = "Adds a level to the specified attribute for someone.", cmdCharSetName = "Changes someone's name to the specified name.", cmdCharGiveItem = "Gives the specified item to someone.", cmdCharKick = "Forcefully makes someone log off of their character.", cmdCharBan = "Prohibits someone from logging into the server with their current character.", cmdCharUnban = "Allows a prohibited character the ability to be used again.", cmdGiveMoney = "Gives a specified amount of money to the person you are looking at.", cmdCharSetMoney = "Changes the total amount of money of someone to the amount specified.", cmdDropMoney = "Drops the specified amount of money in a little box in front of you.", cmdPlyWhitelist = "Allows someone to create a character in the specified faction.", cmdCharGetUp = "Attempts to stand back up after having fallen over.", cmdPlyUnwhitelist = "Disallows someone to create a character in the specified faction.", cmdCharFallOver = "Makes your knees weak and fall over.", cmdBecomeClass = "Attempts to become part of the specified class in your current faction.", cmdCharDesc = "Sets your physical description.", cmdCharDescTitle = "Physical Description", cmdCharDescDescription = "Enter your character's physical description.", cmdPlyTransfer = "Transfers someone to the specified faction.", cmdCharSetClass = "Forcefully makes someone become part of the specified class in their current faction.", cmdMapRestart = "Restarts the map after the specified amount of time.", cmdPanelAdd = "Places a web panel in the world.", cmdPanelRemove = "Removes a web panel that you are looking at.", cmdTextAdd = "Places a block of text in the world.", cmdTextRemove = "Removes blocks of text that you are looking at.", cmdMapSceneAdd = "Adds a cinematic camera viewpoint that is shown in the character menu.", cmdMapSceneRemove = "Removes a camera viewpoint that is shown in the character menu.", cmdSpawnAdd = "Adds a spawn point for the specified faction.", cmdSpawnRemove = "Removes any spawn points you are looking at.", cmdAct = "Performs the %s animation.", cmdContainerSetPassword = "Sets the password of the container you're looking at.", cmdDoorSell = "Sells the door you are looking at.", cmdDoorBuy = "Buys the door you are looking at.", cmdDoorSetUnownable = "Makes the door you are looking at unownable.", cmdDoorSetOwnable = "Makes the door you are looking at ownable.", cmdDoorSetFaction = "Makes the door you are looking at owned by the specified faction.", cmdDoorSetDisabled = "Disallows any commands to be ran on the door you are looking at.", cmdDoorSetTitle = "Sets the title of the door you are looking at.", cmdDoorSetParent = "Sets the parent of a pair of doors.", cmdDoorSetChild = "Sets the child of a pair of doors.", cmdDoorRemoveChild = "Removes the child from a pair of doors.", cmdDoorSetHidden = "Hides the description of the door you are looking at, but still allows it to be ownable.", cmdDoorSetClass = "Makes the door you are looking at owned by the specified class.", cmdMe = "Perform a physical action.", cmdIt = "Make something around you perform an action.", cmdW = "Whisper something to the people near you.", cmdY = "Yell something to the people around you.", cmdEvent = "Make something perform an action that everyone can see.", cmdOOC = "Sends a message in global out-of-character chat.", cmdLOOC = "Sends a message in local out-of-character chat.", iconEditorAlignBest = "Best Guess", iconEditorWidth = "Width", iconEditorHeight = "Height", iconEditorCopy = "Copy to Clipboard", iconEditorCopied = "Copied Item Model Data to Clipboard.", iconEditorAlignFront = "Align from Front", iconEditorAlignAbove = "Align from Above", iconEditorAlignRight = "Align from Right", iconEditorAlignCenter = "Align from Center" } ================================================ FILE: gamemode/languages/sh_french.lua ================================================ -- FRENCH TRANSLATION -- http://steamcommunity.com/id/sadness81 -- https://steamcommunity.com/id/Azphal NAME = "Français" LANGUAGE = { helix = "Helix", introTextOne = "fist industrie vous presente", introTextTwo = "en collaboration avec %s", introContinue = "Appuyez sur ESPACE pour continuer", helpIdle = "Selectionner une catégorie", helpCommands = "Les paramètres de commande avec les sont obligatoires, tandis que les [crochets] sont facultatifs.", helpFlags = "Les drapeaux sur fond vert sont accessibles à ce personnage.", creditSpecial = "Remerciement spécial", creditLeadDeveloper = "Développeur principal", creditUIDesigner = "Concepteur d'interface utilisateur", creditManager = "Chef de projet", creditTester = "Testeur en chef", chatTyping = "Écrit...", chatTalking = "Parle...", chatYelling = "Hurle...", chatWhispering = "Chuchote...", chatPerforming = "Fait une action...", chatNewTab = "Nouvel onglet...", chatReset = "Réinitialise les discussions", chatResetTabs = "Réinitialiser les onglets", chatCustomize = "Personnalise...", chatCloseTab = "Ferme les onglets", chatTabName = "Nom de l'onglet", chatAllowedClasses = "Types de chat acceptés", chatTabExists = "Un onglet de discussion portant ce nom existe déjà !", chatMarkRead = "Tout marquer comme lu", community = "Communauté", checkAll = "Cocher tout", uncheckAll = "Décocher tout", color = "Couleur", type = "Type", display = "Afficher", loading = "Chargement", dbError = "La connexion à la base de données a échouée", unknown = "Inconnu", noDesc = "Aucune description disponible", create = "Créer", update = "Mettre à jour", load = "Charger le personnage", loadTitle = "Charger un personnage", leave = "Quitter le serveur", leaveTip = "Quitter le serveur actuel.", ["return"] = "Revenir", returnTip = "Retourner au menu précédent..", proceed = "Procéder", faction = "Faction", skills = "Compétences", choose = "Choisir", chooseFaction = "Choisir une faction", chooseDescription = "Définissez votre récit", chooseSkills = "Affûtez vos compétences", name = "Nom", description = "Description", model = "Modèle", attributes = "Attribut", attribPointsLeft = "Points restant", charInfo = "Information du personnage", charCreated = "Vous avez créé votre personnage avec succès.", charCreateTip = "Remplissez les champs ci-dessous et appuyez sur 'Terminée' pour créer votre personnage.", invalid = "Vous avez fourni un invalide %s !", nameMinLen = "Votre nom doit contenir au moins %d caractères !", nameMaxLen = "Votre nom ne doit pas dépasser %d caractères !", descMinLen = "Votre description doit contenir au moins %d caractères !", maxCharacters = "Vous ne pouvez pas créer plus de personnage !", player = "Joueur", finish = "Terminée", finishTip = "Terminez la création du personnage.", needModel = "Vous devez choisir un modèle valide !", creating = "Votre personnage est en train d'être créé...", unknownError = "Une erreur inconnue s'est produite !", areYouSure = "Êtes-vous sûr ?", delete = "Supprimer", deleteConfirm = "Ce personnage sera irrévocablement supprimé !", deleteComplete = "%s a été supprimé.", no = "Non", yes = "Oui", close = "Fermer", save = "Sauvegarder", itemInfo = "Nom: %s\nDescription: %s", itemCreated = "Élément(s) créé(s) avec succès.", cloud_no_repo = "Le référentiel fourni n'est pas valide !", cloud_no_plugin = "Le plugin fourni n'est pas valide !", inv = "Inventaire", plugins = "Plugins", author = "Auteur", version = "Version", characters = "Personnages", business = "Marché", settings = "Options", config = "Configuration", chat = "Chat", appearance = "Apparence", misc = "Divers", oocDelay = "Vous devez attendre %s seconde(s) avant de réutiliser le OOC !", loocDelay = "Vous devez attendre %s seconde(s) avant de réutiliser le LOOC !", usingChar = "Vous utilisez déjà ce personnage.", notAllowed = "Vous n'êtes pas autorisé à le faire !", itemNoExist = "L'article que vous avez demandé n'existe pas !", cmdNoExist = "Cette commande n'existe pas !", charNoExist = "Un personnage correspondant n'a pas pu être trouvé !", plyNoExist = "Impossible de trouver un joueur correspondant !", cfgSet = "%s a mis \"%s\" à %s.", drop = "Lâcher", dropTip = "Lâcher cet objet de votre inventaire.", take = "Prendre", takeTip = "Prendre cet objet et le placer dans votre inventaire.", dTitle = "Porte sans propriétaire", dTitleOwned = "Porte achetée", dIsNotOwnable = "Cette porte ne peut être achetée.", dIsOwnable = "Vous pouvez acheter cette porte en appuyant sur F2.", dMadeUnownable = "Vous avez rendu cette porte inachetable.", dMadeOwnable = "Vous avez rendu cette porte achetable.", dNotAllowedToOwn = "Vous n'êtes pas autorisé à posséder cette porte !", dSetDisabled = "Vous avez désactivé cette porte.", dSetNotDisabled = "Vous avez réactivé cette porte.", dSetHidden = "Vous avez caché cette porte.", dSetNotHidden = "Vous avez montré cette porte.", dSetParentDoor = "Vous avez défini cette porte comme porte parent.", dCanNotSetAsChild = "Vous ne pouvez pas définir une porte héritière alors qu'elle est parente !", dAddChildDoor = "Vous avez défini cette porte héritère.", dRemoveChildren = "Vous avez enlevé tout les héritiers de cette porte.", dRemoveChildDoor = "Vous avez enlevé cette porte de son status hértier.", dNoParentDoor = "Aucune porte parent existe.", dOwnedBy = "Cette porte appartient à %s.", dConfigName = "Portes", dSetFaction = "Cette porte appartient désormais à la faction %s.", dRemoveFaction = "Cette porte n'appartient plus à aucune faction.", dNotValid = "Vous ne regardez pas une porte valide !", canNotAfford = "Vous ne pouvez pas vous permettre d'acheter ceci !", dPurchased = "Vous avez acheté cette porte pour %s.", dSold = "Vous avez vendu cette porte pour %s.", notOwner = "Vous n'êtes pas le propriétaire de cela !", invalidArg = "Vous avez fourni une valeur non valide pour l'argument #%s !", invalidFaction = "La faction que vous avez fournie est introuvable !", flagGive = "%s a donné %s '%s' flags.", flagGiveTitle = "Donner Flags", flagTake = "%s a pris les '%s' flags de %s.", flagTakeTitle = "Prend les flags", flagNoMatch = "Vous devez avoir \"%s\" flag(s) pour faire cette action !", textAdded = "Vous avez ajouté un texte.", textRemoved = "Vous avez supprimé le texte de %s.", moneyTaken = "Vous avez reçu %s.", moneyGiven = "Vous avez donné %s.", insufficientMoney = "Tu n'as pas assez pour faire ça !", businessPurchase = "Vous avez acheté %s pour %s.", businessSell = "Vous avez vendu %s pour %s.", businessTooFast = "Veuillez patienter avant d'acheter un autre article !", cChangeModel = "%s a changé le model de %s en %s.", cChangeName = "%s a changé le nom de %s en %s.", cChangeSkin = "%s a changé le skin de %s en %s.", cChangeGroups = "%s a changé le Bodygroup de %s \"%s\" en %s.", cChangeFaction = "%s a transféré %s dans la faction %s.", playerCharBelonging = "Cet objet appartient à votre autre personnage !", spawnAdd = "Vous avez ajouté un spawn pour le %s.", spawnDeleted = "Vous avez supprimé le point d'apparition de %s.", someone = "Quelqu'un", rgnLookingAt = "Permettre à la personne que vous regardez de vous reconnaître.", rgnWhisper = "Permettre à ceux qui entendent vos chuchotements de vous reconnaître.", rgnTalk = "Permettre à ceux qui entendent vos conversations de vous reconnaître.", rgnYell = "Permettre à ceux qui entendent vos cris de vous reconnaître.", icFormat = "%s dit \"%s\"", rollFormat = "%s fait un roll de %s.", wFormat = "%s chuchote \"%s\"", yFormat = "%s hurle \"%s\"", sbOptions = "Cliquez pour voir les options pour %s.", spawnAdded = "Vous avez ajouté un spawn pour %s.", whitelist = "%s a mis dans la whitelist %s de la faction %s.", unwhitelist = "%s a enlevé de la whitelist %s de la faction %s.", noWhitelist = "vous n'avez pas la whitelist pour ce personnage !", gettingUp = "Vous êtes maintenant relevé...", wakingUp = "Vous reprenez conscience...", Weapons = "Arme", checkout = "Aller au Checkpoint (%s)", purchase = "Achat", purchasing = "Achat...", success = "Succès", buyFailed = "Achat raté !", buyGood = "Achat réussi !", shipment = "Expédition", shipmentDesc = "Cet envoi appartient à %s.", class = "Classe", classes = "Classes", illegalAccess = "Accès illégal.", becomeClassFail = "Vous ne pouvez pas devenir un %s!", becomeClass = "Vous êtes devenu un %s.", setClass = "Vous avez défini la classe de %s en %s.", attributeSet = "Vous avez définis %s's %s en %s.", attributeNotFound = "Vous avez spécifié un attribut non valide !", attributeUpdate = "Vous avez ajouté %s's %s par %s.", noFit = "Vous n'avez pas assez d'espace pour cet article !", itemOwned = "Vous ne pouvez pas interagir avec un objet que vous possédez sur un personnage différent !", help = "Aide", commands = "Commande", doorSettings = "Réglages de la porte", sell = "Vendre", access = "Accès", locking = "VERROUILLAGE DE L'ENTITÉ...", unlocking = "DÉVERROUILLAGE DE L'ENTITÉ...", modelNoSeq = "Votre modèle ne supporte pas cet action.", notNow = "Vous n'êtes pas autorisé à le faire ceci maintenant.", faceWall = "Vous devez être face au mur pour faire ceci.", faceWallBack = "Votre dos doit faire face au mur pour faire ceci.", descChanged = "Vous avez changé la description de votre personnage.", noOwner = "Le propriétaire est invalide !", invalidItem = "Vous avez spécifié un élément non valide !", invalidInventory = "Vous avez spécifié un inventaire non valide !", home = "Maison", charKick = "%s a exclu le personnage %s.", charBan = "%s a banni le personnage %s.", charBanned = "Ce personnage est banni.", playerConnected = "%s s'est connecté au serveur.", playerDisconnected = "%s s'est déconnecté du serveur.", setMoney = "Vous avez défini l'argent de %s à %s..", itemPriceInfo = "Vous pouvez acheter cet article pour %s.\nVous pouvez vendre cet article pour %s", free = "Gratuit", vendorNoSellItems = "Il n'y a pas d'objets à vendre.", vendorNoBuyItems = "Il n'y a pas d'objets à acheter.", vendorSettings = "Paramètres du vendeur", vendorUseMoney = "Le vendeur doit utiliser de l'argent ?", vendorNoBubble = "Masquer la bulle du vendeur ?", mode = "Mode", price = "Prix", stock = "Stock", none = "Aucun", vendorBoth = "Acheter et vendre", vendorBuy = "Achat seulement", vendorSell = "Vente seulement", maxStock = "Stock max", vendorFaction = "Éditeur de faction", buy = "Achat", vendorWelcome = "Bienvenue dans mon magasin, que puis-je vous offrir aujourd'hui ?", vendorBye = "Reviens bientôt !", charSearching = "Vous recherchez déjà un autre personnage !", charUnBan = "%s a débanni le personnage %s.", charNotBanned = "Ce personnage n'est pas banni!", quickSettings = "Réglages rapides", vmSet = "Vous avez configuré votre messagerie vocale.", vmRem = "Vous avez supprimé votre messagerie vocale.", noPerm = "Vous n'êtes pas autorisé à faire ceci!", youreDead = "Tu es mort", injMajor = "Semble gravement blessé", injLittle = "Semble blessé", chgName = "Changer de nom", chgNameDesc = "Entrez le nouveau nom du personnage ci-dessous.", weaponSlotFilled = "Vous ne pouvez pas utiliser une autre arme %s!", equippedBag = "Vous ne pouvez pas déplacer un sac contenant un objet équipé !", equippedWeapon = "Vous ne pouvez pas déplacer une arme actuellement équipée !", nestedBags = "Vous ne pouvez pas mettre un inventaire à l'intérieur d'un inventaire de stockage !", outfitAlreadyEquipped = "Tu portes déjà ce genre de tenue !", useTip = "Utiliser l'objet.", equip = "Équiper", equipTip = "Équiper l'objet.", unequip = "Déséquiper", unequipTip = "Déséquiper l'objet.", consumables = "Consommables", plyNotValid = "Vous ne regardez pas un joueur valide !", restricted = "Vous avez été restreint.", salary = "Vous avez reçu %s de votre salaire.", noRecog = "Vous ne reconnaissez pas cette personne.", curTime = "L'heure actuelle est %s.", vendorEditor = "Éditeur du vendeur", edit = "Edit", disable = "Désactiver", vendorPriceReq = "Entrez le nouveau prix pour cet article.", vendorEditCurStock = "Modifier le stock actuel", you = "Vous", vendorSellScale = "Échelle de prix", vendorNoTrade = "Vous ne pouvez pas échanger avec ce marchand !", vendorNoMoney = "Ce marchand ne peut pas se acheter cet article !", vendorNoStock = "Ce marchand n'a pas cet article en stock !", contentTitle = "Contenu Helix manquant", contentWarning = "Le contenu d'Helix n'est pas monté. Certaines fonctionnalités peuvent être manquantes.\nSouhaitez-vous ouvrir la page du Workshop pour le contenu Helix ?", flags = "Flags", mapRestarting = "La carte va redémarrer dans %d secondes !", chooseTip = "Choisissez ce personnage jouer avec.", deleteTip = "Supprimer ce personnage.", storageInUse = "Quelqu'un d'autre utilise ceci !", storageSearching = "Recherche...", container = "Conteneur", containerPassword = "Vous avez défini le mot de passe de ce conteneur en %s.", containerPasswordRemove = "Vous avez supprimé le mot de passe de ce conteneur.", containerPasswordWrite = "Entrer le mot de passe.", containerName = "Vous avez défini le nom de ce conteneur en %s.", containerNameWrite = "Entrez le nom.", containerNameRemove = "Vous avez supprimé le nom de ce conteneur.", containerInvalid = "Vous devez regarder un conteneur pour faire ceci !", wrongPassword = "Vous avez entré un mot de passe incorrect !", respawning = "Réapparition...", scoreboard = "Tableau des joueurs", ping = "Ping: %d", viewProfile = "Voir le profil Steam", copySteamID = "Copier le Steam ID", money = "Argent", moneyLeft = "Votre argent: ", currentMoney = "Argent restant: ", invalidClass = "Ceci n'est pas une classe valide !", invalidClassFaction = "Il ne s'agit pas d'une classe valide pour cette faction !", miscellaneous = "Divers", general = "General", observer = "Observateur", performance = "Performance", thirdperson = "Troisième personne", date = "Date", interaction = "Interaction", server = "Serveur", resetDefault = "Réinitialiser aux valeurs par défaut", resetDefaultDescription = "Ceci réinitialisera \"%s\" à sa valeur par défaut de \"%s\".", optOpenBags = "Sacs ouverts avec inventaire", optdOpenBags = "Visualise automatiquement tous les sacs de votre inventaire lorsque le menu est ouvert.", optShowIntro = "Afficher l'intro au démarrage", optdShowIntro = "Affiche l'introduction de Helix la prochaine fois que vous vous inscrivez. Ceci est toujours désactivé après l'avoir regardé.", optCheapBlur = "Désactiver le flou", optdCheapBlur = "Remplace le flou de l'interface utilisateur par une simple gradation.", optObserverTeleportBack = "Retour à l'emplacement précédent", optdObserverTeleportBack = "Vous ramène à l'emplacement où vous êtes entré en mode observateur.", optObserverESP = "Montrer l'ESP admin", optdObserverESP = "Affiche les noms et les emplacements de chaque joueur sur le serveur.", opt24hourTime = "Utilisez le temps de 24 heures", optd24hourTime = "Afficher les horodatages sur 24 heures au lieu de 12 heures (AM / PM).", optChatNotices = "Afficher les avis dans le chat", optdChatNotices = "Met à la place tous les avis qui apparaissent en haut à droite dans le chat.", optChatTimestamps = "Afficher les horodatages dans le chat", optdChatTimestamps = "Prépose l'heure à chaque message dans la chatbox.", optAlwaysShowBars = "Toujours afficher les barres d'informations", optdAlwaysShowBars = "Affiche les barres d’information en haut à gauche, qu’elles soient masquées ou non.", optAltLower = "Masquer les mains une fois abaissé", -- @todo remove me optdAltLower = "Cacher les mains quand elles sont sur les côtés.", -- @todo remove me optThirdpersonEnabled = "Activer la troisième personne", optdThirdpersonEnabled = "Déplace la caméra derrière toi. Ceci peut également être activé dans la console avec la commande \"ix_togglethirdperson\"", optThirdpersonClassic = "Activer la caméra classique à la troisième personne", optdThirdpersonClassic = "Déplace la vue de ton personnage avec ta souris.", optThirdpersonVertical = "Troisième personne hauteur", optdThirdpersonVertical = "À quelle hauteur la caméra devrait être.", optThirdpersonHorizontal = "Troisième personne horizontale", optdThirdpersonHorizontal = "Quelle distance gauche ou droite la caméra doit être.", optThirdpersonDistance = "Troisième personne distance", optdThirdpersonDistance = "À quelle distance la caméra doit être.", optDisableAnimations = "Désactiver les animations", optdDisableAnimations = "Arrête la lecture des animations pour fournir des transitions instantanées.", optAnimationScale = "Échelle d'animation", optdAnimationScale = "A quelle vitesse les animations doivent être faites.", optLanguage = "Langue", optdLanguage = "La langue affichée dans l'interface utilisateur Helix.", optNoticeDuration = "Durée de la notification", optdNoticeDuration = "Combien de temps afficher les notifications pour (en secondes).", optNoticeMax = "Notification maximum", optdNoticeMax = "Le nombre de notification affichés avant que les précédentes soient supprimés.", optChatFontScale = "Échelle de polices de discussion", optdChatFontScale = "A quelle taille doit être la police de discussion.", optChatOutline = "Décrire le texte du chat", optdChatOutline = "Affiche un contour autour du texte de la discussion plutôt qu’une ombre. Activez cette option si vous rencontrez des difficultés pour lire du texte.", cmdRoll = "Rolls un nombre entre 0 et le nombre spécifié", cmdPM = "Envoie un message privé à quelqu'un.", cmdReply = "Envoie un message privé à la dernière personne à qui vous avez envoyé un message.", cmdSetVoicemail = "Définit ou supprime le message de réponse automatique lorsque quelqu'un vous envoie un message privé.", cmdCharGiveFlag = "Donne le(s) flag(s) spécifié(s) à quelqu'un.", cmdCharTakeFlag = "Supprime le flag spécifié de la personnage si elle le possède.", cmdToggleRaise = "Lève ou baisse l'arme que vous tenez.", cmdCharSetModel = "Définit le modèle de personnage d'une personne.", cmdCharSetSkin = "Définit l'apparence du modèle de personnage d'une personne.", cmdCharSetBodygroup = "Définit le Bodygroup pour le modèle de personnage d'une personne.", cmdCharSetAttribute = "Définit le niveau de l'attribut spécifié pour quelqu'un.", cmdCharAddAttribute = "Ajoute un niveau à l'attribut spécifié pour quelqu'un.", cmdCharSetName = "Remplace le nom de quelqu'un par le nom spécifié.", cmdCharGiveItem = "Donne l'élément spécifié à quelqu'une.", cmdCharKick = "Oblige quelqu'un à se déconnecter de son personnage.", cmdCharBan = "Interdit à une personne de jouer son personnage actuel.", cmdCharUnban = "Permet à un personnage banni d'être utiliser à nouveau.", cmdGiveMoney = "Donne une somme d'argent précise à la personne que vous regardez.", cmdCharSetMoney = "Modifie le montant total d'argent d'une personne en fonction du montant spécifié.", cmdDropMoney = "Dépose le montant d'argent spécifié dans une petite boîte devant vous.", cmdPlyWhitelist = "Permet à quelqu'un de créer un personnage dans la faction spécifiée.", cmdCharGetUp = "Tentatives de se relever après être tombé.", cmdPlyUnwhitelist = "Interdit à quelqu'un de créer un personnage dans la faction spécifiée.", cmdCharFallOver = "Rends vos genoux faibles et vous fait tomber.", cmdBecomeClass = "Tente de faire partie de la classe spécifiée dans votre faction actuelle.", cmdCharDesc = "Définir votre description physique", cmdCharDescTitle = "Description physique", cmdCharDescDescription = "Entrez la description physique de votre personnage.", cmdPlyTransfer = "Transfère quelqu'un à la faction spécifiée.", cmdCharSetClass = "Force quelqu'un à faire partie d'une classe dans sa faction.", cmdMapRestart = "Redémarre la carte après le délai spécifié.", cmdPanelAdd = "Place un panneau Web dans le monde.", cmdPanelRemove = "Supprime un panneau Web que vous regardez.", cmdTextAdd = "Place un bloc de texte dans le monde.", cmdTextRemove = "Supprime les blocs de texte que vous regardez.", cmdMapSceneAdd = "Ajoute un point de vue de caméra cinématique affiché dans le menu du personnage..", cmdMapSceneRemove = "Supprime un point de vue de la caméra qui est affiché dans le menu du personnage.", cmdSpawnAdd = "Ajoute un point d'apparition pour la faction spécifiée.", cmdSpawnRemove = "Supprime les points d'apparition que vous regardez.", cmdAct = "Effectuer l'animation %s.", cmdContainerSetPassword = "Définit le mot de passe du conteneur que vous consultez.", cmdDoorSell = "Vend la porte que vous regardez.", cmdDoorBuy = "Achète la porte que vous regardez.", cmdDoorSetUnownable = "Rend la porte que vous regardez inachetable.", cmdDoorSetOwnable = "Rend la porte que vous regardez achetable.", cmdDoorSetFaction = "Attribut la porte que vous regardez à la faction spécifiée.", cmdDoorSetDisabled = "N'autorise aucune commande sur la porte que vous regardez.", cmdDoorSetTitle = "Définit le titre de la porte que vous regardez.", cmdDoorSetParent = "Définit le parent d'une paire de portes.", cmdDoorSetChild = "Définit l'héritié d'une paire de portes.", cmdDoorRemoveChild = "Enlève l'héritié d'une paire de portes.", cmdDoorSetHidden = "Cache la description de la porte que vous regardez, mais lui permet quand même d'être achetable.", cmdDoorSetClass = "Attribut la porte que vous regardez à la classe spécifiée.", cmdMe = "Effectuer une action physique.", cmdIt = "Décrire une action/situation en tant que narrateur.", cmdW = "Chuchoter quelque chose au gens près de vous.", cmdY = "Crier quelque chose au gens autour de vous.", cmdEvent = "Décrire une action que tout le monde peut voir.", cmdOOC = "Envoie un message dans le chat général en dehors du Rôleplay.", cmdLOOC = "Envoie un message dans le chat local en dehors du Rôleplay." } ================================================ FILE: gamemode/languages/sh_german.lua ================================================ -- GERMAN TRANSLATION NAME = "Deutsch" LANGUAGE = { helix = "Helix", introTextOne = "fist industries präsentiert", introTextTwo = "in Zusammenarbeit mit %s", introContinue = "Drücke Leertaste um fortzufahren", helpIdle = "Wähle eine Kategorie", helpCommands = "Kommandoparameter mit sind Voraussetung, während [Klammern] optional sind.", helpFlags = "Dieser Charakter kann auf Flags mit einem grünen Hintergrund zugreifen.", creditSpecial = "Vielen Dank", creditLeadDeveloper = "Lead Developer", creditUIDesigner = "UI Designer", creditManager = "Project Manager", creditTester = "Lead Tester", chatTyping = "Am Schreiben...", chatTalking = "Am Reden...", chatYelling = "Am Schreien...", chatWhispering = "Am Flüstern...", chatPerforming = "Am Ausführen...", chatNewTab = "Neuer Tab...", chatReset = "Position zurücksetzen", chatResetTabs = "Tabs zurücksetzen", chatCustomize = "Individualisieren...", chatCloseTab = "Tab schließen", chatTabName = "Tab Name", chatNewTabTitle = "Neuer Tab", chatAllowedClasses = "Erlaubte Chatklassen", chatTabExists = "Ein Chat-Tab mit diesem Namen exisitert bereits!", chatMarkRead = "Alles als gelesen markieren.", community = "Community", checkAll = "Alles auswählen", uncheckAll = "Auswahl aufheben", color = "Farbe", type = "Typ", display = "Anzeige", loading = "Am Laden", dbError = "Datenbankverbindung fehlgeschlagen", unknown = "Unbekannt", noDesc = "Keine Beschreibung verfügbar", create = "Erstellen", update = "Update", load = "Charakter laden", loadTitle = "Lade einen Charakter", leave = "Server verlassen", leaveTip = "Verlasse den aktuellen Server", ["return"] = "Zurück", returnTip = "Gehe zum vorherigen Menü zurück", proceed = "Weiter", faction = "Fraktion", skills = "Fähigkeiten", choose = "Auswählen", chooseFaction = "Wähle eine Fraktion", chooseDescription = "Definiere deine Geschichte", chooseSkills = "Verfeinere deine Fähigkeiten", name = "Name", description = "Beschreibung", model = "Model", attributes = "Attribute", attribPointsLeft = "Verbleibende Punkte", charInfo = "Charakter Informationen", charCreated = "Charakter erfolgreich erstellt.", charCreateTip = "Vervollständige alle Felder und klicke Anschließend auf 'Bestätigen' um deinen Charakter zu erstellen.", invalid = "Falscher %s!", nameMinLen = "Dein Name muss mindestens %d Zeichen enthalten!", nameMaxLen = "Deine Name darf maximal %d Zeichen enthalten!", descMinLen = "Deine Beschreibung muss mindestens %d Zeichen enthalten!", maxCharacters = "Du kannst keine weiteren Charakter erstellen!", player = "Spieler", finish = "Bestätigen", finishTip = "Beende die Charaktererstellung", needModel = "Du musst ein gültiges Model auswählen!", creating = "Dein Charakter wird erstellt...", unknownError = "Ein unbekannter Fehler ist aufgetreten!", areYouSure = "Bist du dir sicher?", delete = "Löschen", deleteConfirm = "Dieser Charakter kann nicht wiederhergestellt werden!", deleteComplete = "%s wurde gelöscht.", no = "Nein", yes = "Ja", close = "Schließen", save = "Speichern", itemInfo = "Name: %s\nBeschreibung: %s", itemCreated = "Item(s) erfolgreich erstellt.", cloud_no_repo = "Das gewählte Lager ist nicht gültig!", cloud_no_plugin = "Das gewählte Plugin ist nicht gültig!", inv = "Inventar", plugins = "Plugins", pluginLoaded = "%s hat Plugin \"%s\" für das Laden eines Mapchange aktiviert.", pluginUnloaded = "%s hat Plugin \"%s\" für das Laden eines Mapchange deaktivier.", loadedPlugins = "Geladene Plugins", unloadedPlugins = "Nicht-Geladene Plugins", on = "Aktiviert", off = "Deaktiviert", author = "Author", version = "Version", characters = "Charaktere", business = "Geschäft", settings = "Einstellungen", config = "Konfiguration", chat = "Chat", appearance = "Aussehen", misc = "Sonstiges", oocDelay = "Du musst %s Sekunden warten bevor du OOC erneut benutzt!", loocDelay = "Du musst %s Sekunden warten bevor du LOOC erneut benutzt!", usingChar = "Du benutzt diesen Charakter schon.", notAllowed = "Das darfst du nicht tun!", itemNoExist = "Das angefragte Item existiert nicht!", cmdNoExist = "Dieser Befehl existiert nicht!", charNoExist = "Ein passendes Zeichen wurde nicht gefunden!", plyNoExist = "Ein passendes Spieler wurde nicht gefunden!", cfgSet = "%s hat \"%s\" zu %s gesetzt.", drop = "Fallenlassen", dropTip = "Lasse diese Item fallen.", take = "Nehmen", takeTip = "Nehme dieses Item und packe es in dein Inventar.", dTitle = "Unbekannte Tür", dTitleOwned = "Gekaufte Tür", dIsNotOwnable = "Tür nicht verfügbar", dIsOwnable = "Du kannst diese Tür mit F2 kaufen.", dMadeUnownable = "Diese Tür ist nun nicht mehr verfügbar.", dMadeOwnable = "Diese Tür ist nun wieder verfügbar.", dNotAllowedToOwn = "Du darfst diese Tür nicht besitzen!", dSetDisabled = "Diese Tür ist nun deaktiviert.", dSetNotDisabled = "Diese Tür ist nun wieder aktiviert.", dSetHidden = "Diese Tür ist nun versteckt.", dSetNotHidden = "Diese Tür ist nun sichtbar.", dSetParentDoor = "Du hast diese Tür als deine Parent Tür gesetzt", dCanNotSetAsChild = "Du kannst diese Parent-Tür nicht als Child setzen.", dAddChildDoor = "Du hast diese Tür als Child gesetzt.", dRemoveChildren = "Du hast alle Children dieser Tür entfernt.", dRemoveChildDoor = "Diese Tür ist kein Child mehr.", dNoParentDoor = "Du hast keine Parent-Tür ausgewählt.", dOwnedBy = "%s besitzt diese Tür bereits.", dConfigName = "Türen", dSetFaction = "Fraktion %s besitzt diese Tür.", dRemoveFaction = "Diese Tür gehört nun keine Fraktion mehr.", dNotValid = "Du gucks auf keine Tür!", canNotAfford = "Du hast nicht genug Geld!", dPurchased = "Tür wurde für %s von dir gekauft.", dSold = "Du hast diese Tür für %s verkauft.", notOwner = "Du bist nicht der Besitzer!", invalidArg = "Ungültiger Wert für Argument #%s!", invalidFaction = "Fraktion konnte nicht gefunden werden!", flagGive = "%s hat %s '%s' Berechtigungen gegeben.", flagGiveTitle = "Berechtigungen geben", flagTake = "%s hat '%s' Berechtigungen von %s genommen.", flagTakeTitle = "Berechtigungen nehmen", flagNoMatch = "Du benötigt \"%s\" Berechtigung(en) um diese Aktion auszuführen!", panelAdded = "Feld hinzugefügt.", panelRemoved = "Du hast %d Feld(er) entfernt.", textAdded = "Du hast einen Text hinzugefügt.", textRemoved = "Du hast %s Text(e) gelöscht.", moneyTaken = "Dir wurde %s gegeben.", moneyGiven = "Du hast %s gegeben.", insufficientMoney = "Du hast nicht genug Geld!", businessPurchase = "Du hast %s für %s gekauft.", businessSell = "Du hast %s für %s verkauft.", businessTooFast = "Bitte warte bevor du ein weitere Item kaufst!", cChangeModel = "%s hat %s's Model zu %s geändert.", cChangeName = "%s hat %s's Name zu %s geändert.", cChangeSkin = "%s hat %s's Skin zu %s geändert.", cChangeGroups = "%s hat %s's \"%s\" Bodygroup zu %s geändert.", cChangeFaction = "%s hat %s zu Fraktion %s gewechselt.", playerCharBelonging = "Dieser Gegenstand gehört deinem anderen Charakter!", spawnAdd = "Du hast einen Spawn für %s hinzugefügt.", spawnDeleted = "Du hast %s Spawnpoint(s) entfernt.", someone = "Jemand", rgnLookingAt = "Erlaube allen in Sichtreichweite dich zu erkennen", rgnWhisper = "Erlaube allen in Flüsterreichweite dich zu erkennen.", rgnTalk = "Erlaube allen in Sprachreichweite dich zu erkennen", rgnYell = "Erlaube allen in Rufreichweite dich zu erkennen", icFormat = "%s sagt \"%s\"", rollFormat = "%s hat eine %s von %s gewürfelt.", wFormat = "%s flüstert \"%s\"", yFormat = "%s ruft \"%s\"", sbOptions = "Auswählen um Optionen für %s zu sehen.", spawnAdded = "Du hast einen Spawn für %s hinzugefügt.", whitelist = "%s hat %s auf die Whitelist der Fraktion %s gesetzt.", unwhitelist = "%s hat %s von der Whitelist der Fraktion %s entfernt.", noWhitelist = "Du hast keine Whitelist für diesen Charakter!", charNotWhitelisted = "%s ist nicht auf der Whitelist der Fraktion %s.", gettingUp = "Du stehst auf...", wakingUp = "Du bist wieder bei Bewusstsein...", Weapons = "Waffen", checkout = "Guck dir (%s) an", purchase = "Kaufen", purchasing = "Kaufprozess im Gange...", success = "Erfolgreich", buyFailed = "Kauf fehlgeschlagen!", buyGood = "Kauf erfolgreich!", shipment = "Lieferung", shipmentDesc = "Diese Lieferung gehört %s.", class = "Klasse", classes = "Klassen", illegalAccess = "Unerlaubter Zugriff.", becomeClassFail = "Du kannst kein %s werden!", becomeClass = "Du bist nun %s.", setClass = "Du hast %s's Klasse zu %s gesetzt.", attributeSet = "Du hast %s's %s zu %s gesetzt.", attributeNotFound = "Du hast ein ungültiges Attribut angegeben!", attributeUpdate = "Du hast %s's %s %s hinzugefügt.", noFit = "Du hast kein Platz für dieses Item.", itemOwned = "Du kannst nicht mit einem Item interagieren das ein anderer deiner Charakter besitzt!", help = "Hilfe", commands = "Commands", doorSettings = "Türeinstellungen", sell = "Verkaufen", access = "Zugriff", locking = "Entity am zuschließen...", unlocking = "Entity am aufschließen...", modelNoSeq = "Dein Model unterstütz diese Aktion nicht!", notNow = "Das kannst du nicht tun!", faceWall = "Du musst mit dem Gesicht zu einer Wand stehen!", faceWallBack = "Du musst mit dem Rücken zu einer Wand stehen!", descChanged = "Du hast deine Charakterbeschreibung geändert.", noOwner = "Ungültiger Besitzer!", invalidItem = "Ungültiges Item ausgewählt!", invalidInventory = "Ungültiges Inventar ausgewählt!", home = "Start", charKick = "%s hat Charakter %s gekickt.", charBan = "%s hat Charakter %s gebannt.", charBanned = "Dieser Charakter ist gebannt.", charBannedTemp = "Dieser Charakter ist temporär gebannt.", playerConnected = "%s hat den Server betreten.", playerDisconnected = "%s hat den Server verlassen.", setMoney = "Du hast %s's Geld auf %s gesetzt.", itemPriceInfo = "Du kannst das Item für %s kaufen.\nDu kannst das Item für %s verkaufen.", free = "Kostenlos", vendorNoSellItems = "Es gibt keine Items zu verkaufen.", vendorNoBuyItems = "Es gibt keine Items zu kaufen.", vendorSettings = "Verkäufereinstellungen", vendorUseMoney = "Soll der Verkäufer Geld benutzen?", vendorNoBubble = "Blase verstecken?", category = "Kategorie", mode = "Modus", price = "Preis", stock = "Bestand", none = "Keine", vendorBoth = "Kauf und Verkauf", vendorBuy = "Nur Kauf", vendorSell = "Nur Verkauf", maxStock = "Höchster Bestand", vendorFaction = "Fraktionseditor", buy = "Kaufen", vendorWelcome = "Wilkommen in meinem Laden, wie kann ich behilflich sein?", vendorBye = "Auf Wiedersehen!", charSearching = "Du suchst bereits einen anderen Charakter!", charUnBan = "%s hat Charakter %s entbannt.", charNotBanned = "Dieser Charakter ist nicht gebannt!", quickSettings = "Schnelleinstellungen", vmSet = "Du hast deine Sprachmitteilung gesetzt.", vmRem = "Du hast deiner Sprachmitteilung entfernt.", noPerm = "Das ist dir nicht gestattet!", youreDead = "Du bist tot", injMajor = "Wirkt schwer verletzt", injLittle = "Wirkt leicht verletzt", chgName = "Namen ändern", chgNameDesc = "Gebe den Charakternamen unten ein.", weaponSlotFilled = "Du kannst keine weitere %s Waffe benutzen!", equippedBag = "Du kannst keine Tasche mit einen ausgerüsteten Item bewegen!", equippedWeapon = "Du kannst keine ausgerüstete Waffe bewegen!", nestedBags = "Du kannst kein Inventar in ein anderes Inventar legen!", outfitAlreadyEquipped = "Du trägst dieses Outfit bereits!", useTip = "Benutze das Item.", equipTip = "Rüste das Item aus.", unequipTip = "Lege das Item ab.", consumables = "Verbrauchsgegenstand", plyNotValid = "Du schaust nicht auf einen gültigen Spieler!", restricted = "Du wurdest festgenommen.", salary = "Du hast %s Lohn bekommen.", noRecog = "Du erkennst diese Person nicht.", curTime = "Zeit: %s", vendorEditor = "Verkäufer Einstellungen", edit = "Bearbeiten", disable = "Deaktivieren", vendorPriceReq = "Neuen Preis eingeben.", vendorEditCurStock = "Bestand bearbeiten", vendorStockReq = "Maximalen Bestand für diese Item eingeben.", vendorStockCurReq = "Gebe ein wie viele Items benötigt werden um vom Bestand kaufen zu dürfen.", you = "Du", vendorSellScale = "Verkaufspreis Faktor", vendorNoTrade = "Du kannst mit diesem Verkäufer nicht tauschen!", vendorNoMoney = "Dieser Verkäufer kann sich diese Item nicht leiten!", vendorNoStock = "Dieser Verkäufer hat das Item nicht mehr!", vendorMaxStock = "Dieser Verkäufer hat einen vollen Bestand dieses Items!", contentTitle = "Helix Content fehlt!", contentWarning = "Du hast die Helix-Inhalte nicht installiert. Dies kann dazu führen, dass bestimmte Funktionen fehlen.\nMöchtest du die Workshop-Seite für die Helix-Inhalte öffnen?", flags = "Berechtigungen", mapRestarting = "Die Map wird in %d Sekunden neustarten!", chooseTip = "Wähle einen Charakter zum spielen.", deleteTip = "Lösche diesen Charakter.", storageInUse = "Gegenstand in Nutzung!", storageSearching = "Suche...", container = "Behälter", containerPassword = "Passwort auf %s gesetzt.", containerPasswordRemove = "Du hast das Passwort entfernt.", containerPasswordWrite = "Passwort eingeben.", containerName = "Namen des Behälters auf %s gesetzt.", containerNameWrite = "Namen eingeben.", containerNameRemove = "Name des Behälters entfernt.", containerInvalid = "Du musst einen Behälter anschauen!", wrongPassword = "Falsches Passwort!", respawning = "Wiederbeleben...", tellAdmin = "Informiere ein Teammitglied über diese Fehler: %s", mapAdd = "Du hast eine Mapszene hinzugefügt.", mapDel = "du hast %d Mapszene(n) entfernt.", mapRepeat = "Füge nun den zweiten Punkt hinzu.", scoreboard = "Spieleübersicht", ping = "Ping: %d", viewProfile = "Steamprofil anschauen", copySteamID = "Kopiere Steam ID", money = "Credits", moneyLeft = "Deine Credits: ", currentMoney = "Verbleibende Credits: ", invalidClass = "Das ist keine gültige Klasse!", invalidClassFaction = "Das ist keine gültige Klasse für deine Fraktion!", miscellaneous = "Sonstiges", general = "Allgemein", observer = "Beobachter", performance = "Leistung", thirdperson = "Third Person", date = "Datum", interaction = "Interaktion", server = "Server", resetDefault = "Standarteinstellung wiederherstellen", resetDefaultDescription = "\"%s\" wird auf den Standartwert \"%s\" zurückgesetzt.", optOpenBags = "Öffne Tasche mit Inventar", optdOpenBags = "Öffnet alle Taschen in deinem Inventar automatisch", optShowIntro = "Intro beim Verbinden anzeigen", optdShowIntro = "Zeigt die Helix Einführen beim nächsten Verbinden. Wird nach dem ersten Schauen automatisch deaktiviert.", optCheapBlur = "Deaktiviere Blur", optdCheapBlur = "Ersetzt UI Blur mit einfachem Verdunkeln.", optObserverTeleportBack = "Kehre zur vorherigen Position zurück.", optdObserverTeleportBack = "Kehre zum Ort zurück an dem du in den Beobachtungsmodus gegangen bist.", optObserverESP = "Zeige Admin ESP", optdObserverESP = "Zeige Namen und Position aller Spieler auf dem Server.", opt24hourTime = "Nutze 24-Stundenformat", optd24hourTime = "Zeige Zeitstempel in 24-Stundenformat, statt 12-Stundenformat (AM/PM).", optChatNotices = "Zeige Hinweise im Chat", optdChatNotices = "Zeige alle Hinweise Rechts-Oben stattdessen im Chat an.", optChatTimestamps = "Zeige Zeitstempel im Chat.", optdChatTimestamps = "Zeigt bei jeder Nachricht im Chat die Zeit an.", optAlwaysShowBars = "Zeige Statusbalken dauerhaft.", optdAlwaysShowBars = "Zeige Statusbalken Links-Oben dauerhaft.", optAltLower = "Verstecke Hände wenn runtergenommen.", -- @todo remove me optdAltLower = "Versteckt Händer wenn sie an deinem Körper herunterhängen.", -- @todo remove me optThirdpersonEnabled = "Aktiviere Third-Person", optdThirdpersonEnabled = "Bewegt die Kamera hinter dich. Kann auch mit dem \"ix_togglethirdperson\" Console-Command umgeschaltet werden.", optThirdpersonClassic = "Aktiviere die klassische Third-Person Kamera", optdThirdpersonClassic = "Bewegt die Sicht deines Charakters mit der Maus.", optThirdpersonVertical = "Kamera Höhe", optdThirdpersonVertical = "Wie hoch die Kamera sein soll.", optThirdpersonHorizontal = "Kamera Offset", optdThirdpersonHorizontal = "Wie weit links oder rechts die Kamera sein soll.", optThirdpersonDistance = "Kamera Entfernung", optdThirdpersonDistance = "Wie weit entfernt die Kamera sein soll.", optThirdpersonCrouchOffset = "Camera Höhe beim Kriechen", optdThirdpersonCrouchOffset = "Wie hoch die Kamerahöhe beim Kriechen sein soll.", optDisableAnimations = "Deaktiviere Animationen", optdDisableAnimations = "Verhindert Animationen für einen sofortigen Übergang.", optAnimationScale = "Animation Scale", optdAnimationScale = "Wie schnell Animationen laufen sollen.", optLanguage = "Sprache", optdLanguage = "Die Sprache der Helix UI.", optMinimalTooltips = "Minimale HUD Tips", optdMinimalTooltips = "Ändert die HUD Tips um weniger auffällig zu sein.", optNoticeDuration = "Hinweiszeit", optdNoticeDuration = "Wie lange Hinweise angezeigt werden sollen (in Sekunden).", optNoticeMax = "Maximale Hinweise", optdNoticeMax = "Die maximal gleichzeitig angezeigten Hinweise.", optChatFontScale = "Chat Schiftgröße", optdChatFontScale = "Die Größe der Schirft im Chat.", optChatOutline = "Chat Schirftrand", optdChatOutline = "Größe des Randes um die Schirft im Text.", cmdRoll = "Würfelt eine Zahl zwischen 0 und der angegebenen Zahl.", cmdPM = "Sendet eine Privatnachricht.", cmdReply = "Sendet eine private Antwort zur letzten Person die dich angeschrieben hat.", cmdSetVoicemail = "Setzt oder entfernt die Automatische Nachricht wenn dir jemand eine Privatnachricht sendet.", cmdCharGiveFlag = "Gibt einer Person die angegebenen Berechtigungen.", cmdCharTakeFlag = "Entfernt einer Person die angegebenen Berechtigungen.", cmdToggleRaise = "Hebt oder Senkt die Waffe die du hälst.", cmdCharSetModel = "Setzt das Model einer Person.", cmdCharSetSkin = "Setzt den Skin des Models einer Person.", cmdCharSetBodygroup = "Setzt die Bodygroup des Models einer Person.", cmdCharSetAttribute = "Setzt das Level des angegebenen Attributs einer Person.", cmdCharAddAttribute = "Fügt ein Level zum angegebenen Attirbuts einer Person hinzu.", cmdCharSetName = "Ändert den Namen einer Person.", cmdCharGiveItem = "Gibt das angegebenen Item einer Person.", cmdCharKick = "Wirft jemanden aus seinem Charakter heraus.", cmdCharBan = "Bannt einen bestimmten Charakter.", cmdCharUnban = "Entbannt einen bestimmten Charakter.", cmdGiveMoney = "Gibt dem Spieler den du anschaust eine angegebene Menge Geld.", cmdCharSetMoney = "Setzt das Geld eines Spielers.", cmdDropMoney = "Lässt Geld in einer Box vor dir fallen.", cmdPlyWhitelist = "Whitelistet eine Person zu einer Fraktion.", cmdCharGetUp = "Lässt deine Charakter versuchen wieder aufzustehen.", cmdPlyUnwhitelist = "Entwhitelisted einen Charakter von eine Fraktion.", cmdCharFallOver = "Lässt dich ohnmächtig werden.", cmdBecomeClass = "Versucht die gegebene Klasse in deiner Fraktion zu werden.", cmdCharDesc = "Setzt eine Beschreibung deine Körpers.", cmdCharDescTitle = "Körperbeschreibung", cmdCharDescDescription = "Geben deine Körperbeschreibung ein.", cmdPlyTransfer = "Transferiert einen Spieler zu gegebenen Position.", cmdCharSetClass = "Setzt jemanden zu einer Klasse.", cmdMapRestart = "Führt nach der angegebenen Zeit einen Maprestart aus.", cmdPanelAdd = "Platziert eine Webpanel.", cmdPanelRemove = "Entfernt das Webpanel auf das du schaust.", cmdTextAdd = "Platziert einen Text in die Welt.", cmdTextRemove = "Entfernt den Text auf den du schaust.", cmdMapSceneAdd = "Platziert eine Kamera für eine Mapszene.", cmdMapSceneRemove = "Entfernt eine Kamera für eine Mapszene.", cmdSpawnAdd = "Fügt einen Spawnpoint für die Fraktion hinzu.", cmdSpawnRemove = "Entfernt alle Spawnpoints in deinem Sichtfeld.", cmdAct = "Führt die %s Animation aus.", cmdContainerSetPassword = "Setzt das Passwort des Behälters auf den du schaust.", cmdDoorSell = "Verkauft die tür auf die du schaust.", cmdDoorBuy = "Kauft die tür auf die du schaust.", cmdDoorSetUnownable = "Macht die Tür auf die du schaust nicht-besitzbar.", cmdDoorSetOwnable = "Macht die Tür auf die du schaust wieder beitzbar.", cmdDoorSetFaction = "Setzt die Tür auf die du schaust als Besitzt einer Fraktion", cmdDoorSetDisabled = "Deaktiviert die Tür auf die du schaust.", cmdDoorSetTitle = "Setzt den Namen der Tür auf die du schaust.", cmdDoorSetParent = "Setzt den Parent der Tür auf die du schaust.", cmdDoorSetChild = "Setzt das Child ein Paar von Türen.", cmdDoorRemoveChild = "Entfernt das Child ein Paar von Türen.", cmdDoorSetHidden = "Versteckt die Beschreibung der Tür auf die du schaust.", cmdDoorSetClass = "Setzt die Tür auf die du schaust als Besitz einer bestimmten Klasse.", cmdMe = "Führe eine lokale Aktion aus.", cmdIt = "Führe eine lokale Aktion für ein Objekt in deiner Nähe aus.", cmdW = "Flüstere den Personen in deiner Nähe etwas.", cmdY = "Rufe den Personen um dich etwas zu.", cmdEvent = "Führe eine globale Aktion aus.", cmdOOC = "Sendet eine Nachricht in den globalen Out-of-Character Chat.", cmdLOOC = "Sendet eine Nachricht in den lokalen Out-of-Character Chat.", } ================================================ FILE: gamemode/languages/sh_korean.lua ================================================ NAME = "한국어" LANGUAGE = { loading = "불러오는 중", dbError = "DB 서버 연결 실패", unknown = "알 수 없음", noDesc = "정보가 존재하지 않습니다.", create = "생성", createTip = "새로운 캐릭터를 생성합니다.", load = "계속", loadTip = "플레이할 캐릭터를 불러옵니다.", leave = "종료", leaveTip = "서버에서 퇴장합니다.", ["return"] = "뒤로", returnTip = "이전 메뉴로 돌아갑니다.", name = "이름", description = "정보", model = "외관", attributes = "능력", charCreateTip = "빈칸들을 채우고 아래 '완료' 버튼을 눌러 캐릭터를 생성하십시오.", invalid = "다음 정보가 존재하지 않습니다: %s", descMinLen = "정보는 적어도 %d 자 이상이어야 합니다.", player = "플레이어", finish = "완료", finishTip = "캐릭터 생성을 완료합니다.", needModel = "올바른 외관을 선택하여야 합니다.", creating = "캐릭터를 생성중입니다...", unknownError = "오류가 발생하였습니다.", delConfirm = "%s 영구히 완전히 삭제합니까?", no = "아니오", yes = "예", itemInfo = "이름: %s\n정보: %s", cloud_no_repo = "클라우드 경로가 존재하지 않습니다.", cloud_no_plugin = "클라우드 추가 기능이 존재하지 않습니다.", inv = "인벤토리", plugins = "추가 기능", author = "제작자", version = "버전", characters = "캐릭터", business = "사업", settings = "설정", config = "설정", chat = "대화", appearance = "외관", misc = "기타", oocDelay = "%s 초를 더 기다려야 OOC 대화가 가능합니다.", loocDelay = "%s 초를 더 기다려야 LOOC 대화가 가능합니다.", usingChar = "이미 이 캐릭터로 서버에서 플레이하고 있습니다.", notAllowed = "당신은 이것을 할 권한이 없습니다.", itemNoExist = "당신이 요청한 아이템은 존재하지 않습니다.", cmdNoExist = "당신이 요청한 명령은 존재하지 않습니다.", plyNoExist = "그 이름을 가진 플레이어를 검색할 수 없습니다.", cfgSet = "%s 님이 \"%s\" 를 %s 으로 설정하였습니다.", drop = "버리기", dropTip = "아이템을 소지품에서 제외시킵니다.", take = "가지기", takeTip = "아이템을 소지품에 추가시킵니다.", dTitle = "소유되지 않은 문", dTitleOwned = "소유된 문", dIsNotOwnable = "이 문은 소유할 수 없습니다.", dIsOwnable = "F2를 눌러서 이 문을 소유할 수 있습니다.", dMadeUnownable = "당신은 이 문을 소유할 수 없도록 설정했습니다.", dMadeOwnable = "당신은 이 문을을 소유할 수 있도록 설정했습니다.", dNotAllowedToOwn = "이 문을 소유하도록 허가되지 않았습니다.", dSetDisabled = "당신은 이 문의 기능을 껐습니다.", dSetNotDisabled = "당신은 이 문의 기능을 다시 켰습니다.", dSetHidden = "당신은 이 문을 숨겼습니다.", dSetNotHidden = "당신은 이 문을 숨김 해제했습니다.", dSetParentDoor = "당신은 이 문을 상위 개체로 설정하였습니다.", dCanNotSetAsChild = "당신은 이 문을 하위 개체로 설정할 수 없습니다.", dAddChildDoor = "당신은 이 문을 하위 개체로 설정하였습니다.", dRemoveChildren = "당신은 이 문에 할당된 모든 하위 개체를 삭제했습니다.", dRemoveChildDoor = "당신은 이 문을 하위 개체에서 삭제했습니다.", dNoParentDoor = "상위 개체인 문이 없습니다.", dOwnedBy = "이 문은 %s 님의 소유입니다.", dConfigName = "문", dSetFaction = "이 문은 이제 %s 단체에 속하게 됩니다.", dRemoveFaction = "이 문은 이제 어느 단체에도 속하지 않습니다.", dNotValid = "유효한 문을 바라보고 있어야 합니다.", canNotAfford = "이 문을 구매할 충분한 자금을 가지고 있지 않습니다.", dPurchased = "이 문을 %s으로 구매했습니다.", dSold = "당신은 이 문을 %s으로 판매했습니다.", notOwner = "당신은 이 문을 소유하고 있지 않습니다.", invalidArg = "#%s 번째 명령 변수에 올바른 값을 입력해야 합니다.", invalidFaction = "제시된 이름으로 된 단체를 찾을 수 없습니다.", flagGive = "%s 님이 %s 님에게 '%s' 권한을 주었습니다.", flagGiveTitle = "권한 주기", flagGiveDesc = "이 권한들을 플레이어에게 줍니다.", flagTake = "%s 님이 '%s' 권한을 %s 님으로 부터 받았습니다.", flagTakeTitle = "플래그 가지기.", flagTakeDesc = "이 권한들을 플레이어에게서 뺏습니다.", flagNoMatch = "이 행동은 \"%s\" 권한을 필요로 합니다.", textAdded = "텍스트를 추가하였습니다.", textRemoved = "%s개의 택스트를 삭제하였습니다.", moneyTaken = "%s 발견.", businessPurchase = "당신은 %s 을/를 %s에 구매하였습니다.", businessSell = "당신은 %s 을/를 %s에 판매했습니다.", cChangeModel = "%s님이 %s님의 외관을 교체했습니다: %s.", cChangeName = "%s님이 %s님의 이름을 교체했습니다: %s.", cChangeSkin = "%s 가 %s's 의 스킨을 %s 로 바꾸었습니다.", cChangeGroups = "%s 가 %s 의 \"%s\" 바디그룹을 %s 로 바꾸었습니다.", cChangeFaction = "%s 는 %s 를 %s 팩션으로 이동시켰습니다.", playerCharBelonging = "이 물건은 당신의 다른 캐릭터의 물건입니다.", spawnAdd = "%s 개의 시작지점을 추가하였습니다.", spawnDeleted = "%s개의 시작지점을 삭제하였습니다.", someone = "누군가", rgnLookingAt = "당신이 보고 있는 사람이 당신을 인식하도록 선언.", rgnWhisper = "귓속말 거리에 있는 사람을 당신을 인식하도록 선언", rgnTalk = "일반 대화 거리에 있는 사람을 당신을 인식하도록 선언", rgnYell = "외침 대화 거리에 있는 사람을 당신을 인식하도록 선언", icFormat = "%s: \"%s\"", rollFormat = "%s님이 주사위를 굴렸습니다: %s.", wFormat = "%s(귓속말): \"%s\"", yFormat = "%s(외침): \"%s\"", sbOptions = "%s님에 대한 선택지를 보려면 클릭하십시오.", spawnAdded = "%s 단체를 위한 시작 지점이 추가되었습니다.", whitelist = "%s님이 %s님을 %s 단체에 들어가도록 허가했습니다.", unwhitelist = "%s님이 %s님을 %s 단체에 들어가는 것을 금지했습니다.", gettingUp = "몸을 일으키는 중입니다...", wakingUp = "정신을 차리는 중입니다...", Weapons = "무기", checkout = "물건 결제 (%s)", purchase = "구매", purchasing = "결제 진행중...", success = "성공", buyFailed = "결제 실패.", buyGood = "결제가 완료되었습니다!", shipment = "소유물", shipmentDesc = "이 소유물은 %s님의 명의로 되어있습니다.", class = "직업", classes = "직업", illegalAccess = "잘못된 접근입니다.", becomeClassFail = "%s이/가 되는 것에 실패했습니다.", becomeClass = "%s이/가 되었습니다.", attributeSet = "당신은 %s님의 %s을/를 %s로 설정하였습니다.", attributeUpdate = "당신은 %s님의 %s을/를 %s만큼 추가하였습니다.", noFit = "소지품 공간이 부족합니다.", help = "도움말", commands = "명령어", helpDefault = "목차 선택", doorSettings = "문 설정", sell = "판매", access = "접근", locking = "이 물건을 잠그는 중입니다...", unlocking = "이 물건을 여는 중입니다...", modelNoSeq = "당신의 외관은 이 행동을 지원하지 않습니다.", notNow = "당신은 아직 이 행동을 할 수 없습니다.", faceWall = "이 행동을 위해선 벽을 바라보고 있어야 합니다.", faceWallBack = "이 행동을 위해선 벽을 등지고 있어야 합니다.", descChanged = "당신의 캐릭터의 정보를 변경했습니다.", charMoney = "당신의 소지금은 %s 입니다.", charFaction = "당신은 %s 단체에 소속되어 있습니다.", charClass = "당신은 이 단체의 %s 입니다.", noOwner = "소유자가 존재하지 않습니다.", invalidIndex = "아이템의 구분 번호가 올바르지 않습니다.", invalidItem = "아이템 객체 참조가 잘못되었습니다.", invalidInventory = "소지품 객체 참조가 잘못되었습니다.", home = "초기", charKick = "%s님이 %s님의 캐릭터를 추방하였습니다.", charBan = "%s님이 %s님의 캐릭터를 영구히 추방하였습니다.", charBanned = "이 캐릭터는 사용이 금지되었습니다.", setMoney = "당신은 %s님의 돈을 %s으로 설정하였습니다.", itemPriceInfo = "이 아이템을 %s에 구매가 가능합니다.\n이 아이템을 %s에 탄매가 가능합니다", free = "무료", vendorNoSellItems = "판매할 아이템이 없습니다.", vendorNoBuyItems = "구매할 아이템이 없습니다.", vendorSettings = "상인 설정", vendorUseMoney = "상인에 제한된 돈", vendorNoBubble = "말풍선 보이기", mode = "상태", price = "가격", stock = "재고", none = "없음", vendorBoth = "판매와 구매", vendorBuy = "구매 전용", vendorSell = "판매 전용", maxStock = "최대 재고", vendorFaction = "팩션 에디터", buy = "구매", vendorWelcome = "어서오세요. 무엇을 찾으십니까?", vendorBye = "다음에 또 오세요!", charSearching = "이미 캐릭터를 수색하고 있습니다.", charUnBan = "%s 님이 다음 캐릭터를 금지 해제했습니다: %s.", charNotBanned = "이 캐릭터는 금지되지 않았습니다.", containerPassword = "이 보관함의 암호를 %s 으로 설정하였습니다.", containerPasswordRemove = "이 보관함의 암호를 삭제했습니다.", containerPasswordWrite = "암호를 입력해 주십시오.", wrongPassword = "암호가 다릅니다.", cheapBlur = "블러 효과 사용 (FPS 향상)", quickSettings = "빠른 설정", vmSet = "개인 귓속말을 설정했습니다.", vmRem = "개인 귓속말을 삭제했습니다.", altLower = "주먹 미사용시 숨김", noPerm = "이 행위를 할 권한이 없습니다.", youreDead = "당신은 죽었습니다", injMajor = "중상을 입음.", injLittle = "부상을 입음.", toggleESP = "어드민 월핵 사용", chgName = "이름 변경", chgNameDesc = "아래에 캐릭터의 새로운 이름을 입력하세요.", thirdpersonToggle = "3인칭 사용", thirdpersonClassic = "클래식 3인칭 사용", equippedBag = "가방 내부에 사용중인 아이템이 있습니다.", useTip = "이 아이템을 사용합니다.", equipTip = "이 아이템을 착용합니다.", unequipTip = "이 아이템을 착용해제합니다.", consumables = "소모품", plyNotValid = "당신은 잘못된 플레이어를 보고있습니다.", restricted = "당신은 저지되었습니다.", viewProfile = "스팀 프로필 보기", salary = "당신은 월급으로 부터 &s 만큼의 돈이 들어왔습니다.", noRecog = "당신은 이 사람을 인식하지 않았습니다.", curTime = "지금 시각은 %s.", vendorEditor = "상인 수정", edit = "수정", disable = "해제", vendorPriceReq = "이 물품의 새로운 가격을 적으십시오.", vendorEditCurStock = "현재 재고 수정", you = "당신", vendorSellScale = "판매 가격 규모", vendorNoTrade = "당신은 이 상인과 거래 할수없습니다.", vendorNoMoney = "이 상인은 해당 물품을 사 들일수 없습니다.", vendorNoStock = "이 상인은 해당 물품의 재고가 없습니다.", contentTitle = "Helix 콘텐츠 없음.", contentWarning = "당신은 Helix 콘텐츠가 적용되어있지 않습니다. 특정 기능이 누락될 수 있습니다.\nHelix 콘텐츠를 적용해야 합니다.", flags = "플래그" } ================================================ FILE: gamemode/languages/sh_norwegian.lua ================================================ NAME = "Norwegian" LANGUAGE = { loading = "Laster", dbError = "Database tilkobling feilet", unknown = "Ukjent", noDesc = "Ingen beskrivelse tilgjengelig", create = "Lag", createTip = "Lag en ny karakter til å spille med.", load = "Last", loadTip = "Velg en tidligere brukt karakter til å spille med.", leave = "Forlat", leaveTip = "Forlat den nåværende serveren.", ["return"] = "Tilbake", returnTip = "Tilbake til den forrige menyen.", name = "Navn", description = "Beskrivelse", model = "Modell", attributes = "Attributter", charCreateTip = "Fyll inn feltene nedenfor og trykk på 'Fullfør' for å skape din karakter.", invalid = "Du har gitt et ugyldig %s", descMinLen = "Din beskrivelse må være minst %d bokstav(er).", player = "Spiller", finish = "Fullfør", finishTip = "Fullfør med å lage karakteren.", needModel = "Du må velge en gyldig modell", creating = "Din karakter blir skapt...", unknownError = "Det har oppstått en ukjent feil", delConfirm = "Er du sikker på at du vil PERMANENT slette %s?", no = "Nei", yes = "Ja", itemInfo = "Navn: %s\nDescription: %s", cloud_no_repo = "The repository provided is not valid.", cloud_no_plugin = "The plugin provided is not valid.", inv = "Inventar", plugins = "Tillegg", author = "Forfatter", version = "Versjon", characters = "Karakterer", business = "Handel", settings = "Innstillinger", config = "Konfigurasjon", chat = "Chat", appearance = "Utseende", misc = "Diverse", oocDelay = "Du må vente %s sekund(er) med å bruke OOC igjen.", loocDelay = "Du må vente %s sekund(er) med å bruke LOOC igjen.", usingChar = "Du bruker allerede denne karakteren.", itemNoExist = "Beklager, det elementet du forespurte finnes ikke.", cmdNoExist = "Beklager, den kommandoen ekisterer ikke.", plyNoExist = "Beklager, en matchende spiller ble ikke funnet.", cfgSet = "%s har satt \"%s\" til %s.", drop = "Frigjør", dropTip = "Frigjør dette elementet fra ditt inventar.", take = "Ta", takeTip = "Ta dette elementet og putt det i inventaret ditt.", dTitle = "Ueid Dør", dTitleOwned = "Kjøpt Dør", dIsNotOwnable = "Denne døren er ikke mulig å kjøpe.", dIsOwnable = "Du kan kjøpe denne døren med å trykke på F2.", dMadeUnownable = "Du har gjordt denne døren umulig å kjøpe.", dMadeOwnable = "Du har gjordt denne døren mulig å kjøpe.", dNotAllowedToOwn = "Du er ikke tillatt å eie denne døra.", dSetDisabled = "Du har deaktivert denne døren.", dSetNotDisabled = "Du har gjordt denne døren ikke lenger deaktivert.", dSetHidden = "Du har gjordt denne døren gjemt.", dSetNotHidden = "Du har gjordt at døren ikke er gjemt lenger.", dSetParentDoor = "Du har gjort denne døren, hoveddøren.", dCanNotSetAsChild = "Du kan ikke sette hoveddøren som sekundær døren.", dAddChildDoor = "Du har lagt til en sekundær dør.", dRemoveChildren = "Du har fjernet alle sekundære dører fra denne døren.", dRemoveChildDoor = "Du har fjernet denne døren fra å være en sekundær dør.", dNoParentDoor = "Du har ikke en hoveddør satt.", dOwnedBy = "Denne døren er eid av %s.", dConfigName = "Dører", dSetFaction = "Denne døren tilhører %s gruppen.", dRemoveFaction = "Denne døren tilhører ikke en gruppe lenger.", dNotValid = "Du ser ikke på en gyldig dør.", canNotAfford = "Du har ikke råd til å kjøpe dette.", dPurchased = "Du har kjøpt denne døren for %s.", dSold = "Du har solgt denne døren for %s.", notOwner = "Du er ikke eieren av dette.", invalidArg = "Du har gitt en ugyldig verdi for argumentet #%s.", flagGive = "%s har gitt %s '%s' flaggene.", flagGiveTitle = "Gi Flagg", flagGiveDesc = "Gi de følgene flaggene til en spiller.", flagTake = "%s har tatt '%s' flaggene fra %s.", flagTakeTitle = "Ta Flagg", flagTakeDesc = "Fjern de følgene flaggene til en spiller.", flagNoMatch = "Du må ha \"%s\" Flagg for å gjøre denne handlingen.", textAdded = "Du har lagt til en tekst.", textRemoved = "Du har fjernet %s tekst(er).", moneyTaken = "Du har funnet %s.", businessPurchase = "Du har kjøpt %s for %s.", businessSell = "Du har solgt %s for %s.", cChangeModel = "%s endret %s's modell til %s.", cChangeName = "%s endret %s's navn til %s.", cChangeSkin = "%s endret %s's skin til %s.", cChangeGroups = "%s endret %s's \"%s\" kroppsgruppe to %s.", cChangeFaction = "%s har overført %s til %s gruppen.", playerCharBelonging = "Dette objektet tilhører en av dine andre karakterer.", invalidFaction = "Du har gitt en ugyldig gruppe.", spawnAdd = "Du har lagt til spawnen for %s.", spawnDeleted = "Du har fjernet %s spawn punkt(er).", someone = "Noen", rgnLookingAt = "Tillat personen du ser på å gjenkjenne deg.", rgnWhisper = "Tillat de innen hviske radius å gjenkjenne deg.", rgnTalk = "Tillat de innen prate radius å gjenkjenne deg.", rgnYell = "Tillat de innen rope radius å gjenkjenne deg.", icFormat = "%s sier \"%s\"", rollFormat = "%s har rullet %s.", wFormat = "%s hvisker \"%s\"", yFormat = "%s roper \"%s\"", sbOptions = "Klikk for å se instillingene for %s.", spawnAdded = "Du har lagt til spawnen for %s.", whitelist = "%s har hvitelistet %s for %s gruppen.", unwhitelist = "%s har fjernet %s fra hvitelisten til %s gruppen.", gettingUp = "Du reiser deg opp...", wakingUp = "Du er kommer til bevissthet...", Weapons = "Våpen", checkout = "Gå til kassen (%s)", purchase = "Kjøp", purchasing = "Kjøp = er...", success = "Suksess", buyFailed = "Kjøpet mislyktes.", buyGood = "Kjøp vellykket!", shipment = "Forsendelsen", shipmentDesc = "Denne forsendelsen tilhører %s.", class = "Klasse", classes = "Klasser", illegalAccess = "Ulovlig Tilgang.", becomeClassFail = "Klarte ikke å bli %s.", becomeClass = "Du har bltt %s.", attributeSet = "Du har satt %s's %s til %s.", attributeUpdate = "Du har lagt til %s's %s av %s.", noFit = "Dette elementet kan ikke passe i inventaret ditt.", help = "Hjelp", commands = "Kommandoer", helpDefault = "Velg et katagori", doorSettings = "Dør innstillinger", sell = "Selg", access = "Tilgang", locking = "Låser denne enheten...", unlocking = "Låser opp denne enheten...", modelNoSeq = "Din modell støtter ikke denne handlingen.", notNow = "Du er ikke tillatt.", faceWall = "Du må stå mot veggen for å gjøre dette.", faceWallBack = "Din rygg må stå mot veggen for å gjøre dette.", descChanged = "Du har endret din karakters beskrivelse.", charMoney = "Du har akkurat nå %s.", charFaction = "Du er et medlem av denne %s gruppen.", charClass = "Du er %s i gruppen.", noOwner = "Eieren er ugyldig.", notAllowed = "Denne handlingen er ikke tillatt.", invalidIndex = "Elementet's Index er ugyldig.", invalidItem = "Element Objektet er ugyldig.", invalidInventory = "Inventar objektet er ugyldig.", home = "Hjem", charKick = "%s sparket karakteren %s.", charBan = "%s utestengte karakteren %s.", charBanned = "Denne karakteren er utestengt.", setMoney = "Du har satt %s's penger til %s.", itemPriceInfo = "Du kan kjøpe dette elementet for %s.\nDu kan kjøpe dette elementet for %s", free = "Gratis", vendorNoSellItems = "Det er ingen elementer for å selle.", vendorNoBuyItems = "Det er ingen elementer til å kjøpe.", vendorSettings = "Leverandør Innstillinger", vendorUseMoney = "Leverandør skal bruke penger?", vendorNoBubble = "Gjem leverandør bobblen?", mode = "Modus", price = "Pris", stock = "På lager", none = "Ingen", vendorBoth = "Kjøp og selg", vendorBuy = "Kun Kjøp", vendorSell = "Kun Selg", maxStock = "Maks på lager", vendorFaction = "Gruppe endrer", buy = "Kjøp", vendorWelcome = "Velkommen til butikken min, hva kan jeg gi deg i dag?", vendorBye = "Kom tilbake snart!", charSearching = "Du søker allerede etter en annen karakter, vennligst vent.", charUnBan = "%s har fjernet %s fra karakter utestengt listen.", charNotBanned = "Denne karakteren er ikke utestengt.", containerPassword = "Du har satt dette lagerets passord til %s.", containerPasswordRemove = "Du har fjernet dette lagerets passord.", containerPasswordWrite = "Skriv inn passordet.", wrongPassword = "Du har skrivd inn feil passord.", cheapBlur = "Deaktiver uklarhet? (Bedre FPS)", quickSettings = "Hurtiginnstillinger", vmSet = "Du har satt ditt mobilsvar.", vmRem = "Du har fjernet ditt mobilsvar.", altLower = "Gjemme hendene når senket?", noPerm = "Du er ikke tillatt til å gjøre dette.", youreDead = "Du er død.", injMajor = "Ser kritisk skadd ut.", injLittle = "Ser skadd ut.", toggleESP = "Veksle Admin ESP", chgName = "Endre navn", chgNameDesc = "Skriv in karakterens nye navn under.", thirdpersonToggle = "Veksle Tredje-Person", thirdpersonClassic = "Bruk klassisk Tredje-Person", equippedBag = "Posen at du flyttet har utstyrt element.", useTip = "Bruk dette elementet.", equipTip = "Ta på dette elementet.", unequipTip = "Ta av dette elementet.", consumables = "Forbruksvarer", plyNotValid = "Du ser ikke på en gyldig spiller.", restricted = "Du har blitt tilbakeholdne.", viewProfile = "Se Steam Profil", salary = "Du har motatt %s fra lønnen din.", noRecog = "Du kjenner ikke denne personen.", curTime = "Tiden er %s.", vendorEditor = "Leverandør Redigering", edit = "Rediger", disable = "Deaktiver", vendorPriceReq = "Skriv in den nye prisen for dette elementet.", vendorEditCurStock = "Rediger nåværende lager", you = "Du", vendorSellScale = "Utsalgspris skala", vendorNoTrade = "Du er ikke i stand til å handle med denne leverandøren.", vendorNoMoney = "Denne leverandøren ikke har råd til dette elementet.", vendorNoStock = "Denne leverandøren har ikke at varen på lager.", contentTitle = "Helix Innhold Mangler", contentWarning = "Du har ikke Helix innhold montert. Dette kan føre til enkelte funksjoner mangler. \nVil du åpne Workshop side for Helix innhold?", flags = "Flagg" } ================================================ FILE: gamemode/languages/sh_polish.lua ================================================ -- Autorzy: zgredinzyyy (Poprawki + Brakujące rzeczy) || Veran120, Michał, Lechu2375 https://github.com/lechu2375/helix-polishlocalization/blob/master/sh_polish.lua NAME = "Polski" LANGUAGE = { helix = "Helix", introTextOne = "fist industries prezentuje", introTextTwo = "w współpracy z %s", introContinue = "wciśnij spację by kontynuować", helpIdle = "Wybierz kategorię", helpCommands = "Komendy z parametrami w są wymagane, a w [nawiasach kwadratowych] są opcjonalne", helpFlags = "Flagi z zielonym tłem są dostępne przez tą postać.", creditSpecial = "Podziękowania dla", creditLeadDeveloper = "Główny Developer", creditUIDesigner = "UI Designer", creditManager = "Project Manager", creditTester = "Lead Tester", chatTyping = "Pisze...", chatTalking = "Mówi...", chatYelling = "Krzyczy...", chatWhispering = "Szepcze...", chatPerforming = "Wykonuje...", chatNewTab = "Nowa karta", chatReset = "Zresetuj pozycję", chatResetTabs = "Resetuj karty", chatCustomize = "Personalizuj...", chatCloseTab = "Zamknij kartę", chatTabName = "Nazwa karty", chatAllowedClasses = "Dostępne Klasy czatów", chatTabExists = "Karta z taką nazwą już instnieje!", chatMarkRead = "Odznacz wszystko jako przeczytane", community = "Community", checkAll = "Zaznacz wszystko", uncheckAll = "Odznacz wszystko", color = "Kolor", type = "Typ", display = "Wyświetlanie", loading = "Ładowanie", dbError = "Połączenie z bazą danych nie powiodło się", unknown = "Nieznane", noDesc = "Opis niedostępny", create = "Stwórz", update = "Zaktualizuj", load = "Załaduj postać", loadTitle = "Załaduj swoją postać", leave = "Opuść serwer", leaveTip = "Opuść ten serwer.", ["return"] = "Powrót", returnTip = "Powróć do poprzedniego menu.", proceed = "Kontynuuj", faction = "Frakcja", skills = "Umiejętności", choose = "Wybierz", chooseFaction = "Wybierz Frakcje", chooseDescription = "Zdefiniuj swoją postać", chooseSkills = "Dostosuj swoje umiejętności", name = "Imię i Nazwisko", description = "Opis", model = "Model", attributes = "Atrybuty", attribPointsLeft = "Pozostałe punkty", charInfo = "Informacje o postaci", charCreated = "Udało ci się stworzyć swoją postać.", charCreateTip = "Wypełnij pola poniżej i klinij 'Zakończ' aby stworzyć swoją postać.", invalid = "Podałeś niewłaściwe(ą) %s", nameMinLen = "Twoje imię musi zawierać conajmniej %d znaków!", nameMaxLen = "Twoje imię nie może posiadać więcej niż %d znaków!", descMinLen = "Twoje opis musi zawierać conajmniej %d znaków!", maxCharacters = "Nie możesz utworzyć więcej postaci!", player = "Gracz", finish = "Zakończ", finishTip = "Ukończ tworzenie postaci.", needModel = "Musisz wybrać poprawny model!", creating = "Twoja postać jest aktualnie tworzona...", unknownError = "Wystąpił nieznany błąd!", areYouSure = "jesteś pewien?", delete = "Usuń", deleteConfirm = "Ta postać będzie bezpowrotnie usunięta!", deleteComplete = "%s został(a) usunięty(a).", no = "Nie", yes = "Tak", close = "Zamknij", save = "Zapisz", itemInfo = "Imię Nazwisko: %s\nOpis: %s", itemCreated = "Przedmiot(y) pomyślnie utworzony.", cloud_no_repo = "Repozytorium, które zostało podane jest nieprawidłowe.", cloud_no_plugin = "Podany plugin jest nieprawidłowy.", inv = "Ekwipunek", plugins = "Pluginy", pluginLoaded = "%s włączył \"%s\" plugin na następne załadowanie mapy.", pluginUnloaded = "%s wyłączył \"%s\" plugin z następnego załadowania mapy.", loadedPlugins = "Załadowane Pluginy", unloadedPlugins = "Niezaładowane Pluginy", on = "On", off = "Off", author = "Autor", version = "Wersja", characters = "Postacie", business = "Biznes", settings = "Opcje", config = "Konfiguracja", chat = "Czat", appearance = "Wygląd", misc = "Różne", oocDelay = "Musisz poczekać %s sekund przed ponownym użyciem OOC.", loocDelay = "Musisz poczekać %s sekund przed ponownym użyciem LOOC.", usingChar = "Aktualnie używasz tej postaci.", notAllowed = "Przepraszamy, nie masz uprawnień do zrobienia tego.", itemNoExist = "Przedmiot o który prosiłeś nie istnieje.", cmdNoExist = "Taka komenda nie istnieje.", charNoExist = "Nie znaleziono pasującej postaci.", plyNoExist = "Nie znaleziono pasującego gracza.", cfgSet = "%s ustawił \"%s\" na %s.", drop = "Wyrzuć", dropTip = "Upuszcza ten przedmiot z twojego ekwipunku.", take = "Podnieś", takeTip = "Weź ten przedmiot i umieść go w swoim ekwipunku.", dTitle = "Drzwi do kupienia", dTitleOwned = "Wykupione Drzwi", dIsNotOwnable = "Tych drzwi nie można kupić.", dIsOwnable = "Możesz kupić te drzwi naciskając F2.", dMadeUnownable = "Uczyniłeś te drzwi niemożliwymi do kupienia.", dMadeOwnable = "Uczyniłeś te drzwi możliwymi do kupienia.", dNotAllowedToOwn = "Nie możesz kupić tych drzwi.", dSetDisabled = "Wyłączyłeś te drzwi z użytku.", dSetNotDisabled = "Ponownie można używać tych drzwi.", dSetHidden = "Schowałeś te drzwi.", dSetNotHidden = "Usunąłeś te drzwi z ukrytych.", dSetParentDoor = "Uczyniłeś te drzwi swoimi drzwiami nadrzędnymi.", dCanNotSetAsChild = "Nie możesz ustawi aby drzwi nadrzędne były drzwiami podrzędnymi.", dAddChildDoor = "You have added a this door as a child.", dRemoveChildren = "Usunąłeś wszystkie drzwi podrzędne należące do tych drzwi.", dRemoveChildDoor = "Te drzwi już nie są drzwiami podrzędnymi.", dNoParentDoor = "Nie masz ustawionych drzwi nadrzędnych.", dOwnedBy = "Te drzwi należą do %s.", dConfigName = "Drzwi", dSetFaction = "Te drzwi należą teraz do frakcji %s.", dRemoveFaction = "Te drzwi już nie należą do żadnej frakcji.", dNotValid = "Nie patrzysz na prawidłowe drzwi.", canNotAfford = "Nie stać Cię na kupienie tego.", dPurchased = "Kupiłeś te drzwi za %s.", dSold = "Sprzedałeś te drzwi za %s.", notOwner = "Nie jesteś właścicielem tego.", invalidArg = "Podałeś niepoprawną wartość dla argumentu #%s.", invalidFaction = "Frakcja, którą podałeś nie została znaleziona!", flagGive = "%s dał %s następujące flagi: '%s'.", flagGiveTitle = "Daj Flagi", flagTake = "%s zabrał od %s następujące flagi: '%s'.", flagTakeTitle = "Zabierz Flagi", flagNoMatch = "Musisz posiadać flagę(i) \"%s\" aby wykonać tą czynność.", panelAdded = "Dodałeś nowy panel.", panelRemoved = "Usunąłęś %d panel(e)", textAdded = "Dodałeś tekst.", textRemoved = "Usunąłeś %s tekst(y).", moneyTaken = "Znalazłeś %s.", moneyGiven = "Otrzymałeś %s.", insufficientMoney = "Nie posiadasz tyle środków!", businessPurchase = "Kupiłeś %s za %s.", businessSell = "Sprzedałeś %s za %s.", businessTooFast = "Zaczekaj przed następnym kupnem!", cChangeModel = "%s zmienił model gracza %s na %s.", cChangeName = "%s zmienił imię gracza %s na %s.", cChangeSkin = "%s zmienił model %s na %s.", cChangeGroups = "%s zmienił bodygroupy %s \"%s\" na %s.", cChangeFaction = "%s przeniósł %s do frakcji %s.", playerCharBelonging = "Ten przedmiot należy do innej postaci należącej do Ciebie.", spawnAdd = "Dodałeś punkt odradzania dla %s.", spawnDeleted = "Usunąłeś %s punkt(y) odradzania się.", someone = "Ktoś", rgnLookingAt = "Pozwól osobie na którą patrzysz, aby Cię rozpoznawała.", rgnWhisper = "Pozwól tym, którzy są w zasięgu Twoich szeptów, aby Cię rozpoznawali.", rgnTalk = "Pozwól tym, którzy są w zasięgu normalnych rozmów, aby Cię rozpoznawali.", rgnYell = "Pozwól tym, którzy są w zasięgu Twoich krzyków, aby Cię rozpoznawali.", icFormat = "%s mówi: \"%s\"", rollFormat = "%s wylosował %s.", wFormat = "%s szepcze: \"%s\"", yFormat = "%s krzyczy: \"%s\"", sbOptions = "Kliknij aby zobaczyć opcje dla %s.", spawnAdded = "Dodałeś punkt odradzania dla %s.", whitelist = "%s dodał %s na whitelistę frakcji %s.", unwhitelist = "%s usunął %s z whitelisty frakcji %s.", noWhitelist = "Nie masz dostępu do whitelisty na tą postać!", gettingUp = "Podnosisz się...", wakingUp = "Wraca Ci świadomość...", Weapons = "Broń", checkout = "Idź do kasy (%s)", purchase = "Kup", purchasing = "Kupuję...", success = "Sukces", buyFailed = "Zakupy nie powiodły się.", buyGood = "Zakupy udane!", shipment = "Dostawa", shipmentDesc = "Ta dostawa należy do %s.", class = "Klasa", classes = "Klasy", illegalAccess = "Nielegalny Dostęp.", becomeClassFail = "Nie udało Ci się zostać %s.", becomeClass = "Zostałeś %s.", setClass = "Ustawiłeś klasę %s na %s.", attributeSet = "Ustawiłeś %s %s na %s.", attributeNotFound = "You have specified an invalid attribute!", attributeUpdate = "Podwyższyłeś %s %s o %s.", noFit = "Nieposiadasz wystarczająco miejsca, aby zmieścić ten przedmiot!", itemOwned = "Nie możesz wchodzić w interakcje z przedmiotami, które posiadasz jako inna postać!", help = "Pomoc", commands = "Komendy", doorSettings = "Ustawienia Drzwi", sell = "Sprzedaj", access = "Dostęp", locking = "Blokowanie tego przedmiotu...", unlocking = "Odblokowywanie tego przedmiotu...", modelNoSeq = "Twój model nie obsługuje tej animacji.", notNow = "Nie możesz tego aktualnie zrobić.", faceWall = "Musisz patrzeć na ścianę aby to wykonać.", faceWallBack = "Musisz stać tyłem do ściany aby to wykonać.", descChanged = "Zmieniłeś rysopis swojej postaci.", noOwner = "Nieprawidłowy właściciel.", invalidItem = "Wskazałeś nieprawidłowy przedmiot!", invalidInventory = "Wskazałeś nieprawidłowy ekwipunek!", home = "Strona Główna", charKick = "%s wyrzucił %s.", charBan = "%s zablokował postać %s.", charBanned = "Ta postać jest zablokowana.", charBannedTemp = "Ta postać jest tymczasowo zablokowana.", playerConnected = "%s połączył się z serwerem.", playerDisconnected = "%s wyszedł z serwera.", setMoney = "Ustawiłeś ilość pieniędzy %s na %s.", itemPriceInfo = "Możesz kupić ten przedmiot za %s.\nMożesz sprzedać ten przedmiot za %s", free = "Darmowe", vendorNoSellItems = "Nie ma przedmiotów do sprzedania.", vendorNoBuyItems = "Nie ma przedmiotów do kupienia.", vendorSettings = "Ustawienia sprzedawców", vendorUseMoney = "Czy sprzedawcy powinni używać pieniędzy?", vendorNoBubble = "Ukryć dymek sprzedawcy?", mode = "Tryb", price = "Cena", stock = "Zasób", none = "Nic", vendorBoth = "Kupowanie i Sprzedawanie", vendorBuy = "Tylko kupowanie", vendorSell = "Tylko sprzedawanie", maxStock = "Maksymalny zasób", vendorFaction = "Edytor frakcji", buy = "Kup", vendorWelcome = "Witaj w moim sklepie, czy mogę Ci coś podać?", vendorBye = "Przyjdź niedługo z powrotem!", charSearching = "Aktualnie szukasz już innej postaci, proszę poczekać.", charUnBan = "%s odblokował postać %s.", charNotBanned = "Ta postać nie jest zablokowana.", quickSettings = "Szybkie Ustawienia", vmSet = "Ustawiłeś swoją pocztę głosową.", vmRem = "Usunąłęś swoją pocztę głosową.", noPerm = "Nie możesz tego zrobić!", youreDead = "Jesteś martwy", injMajor = "Widoczne krytyczne obrażenia.", injLittle = "Widoczne obrażenia.", chgName = "Zmień Imię i Nazwisko.", chgNameDesc = "Wprowadź nowę imię i nazwisko postaci poniżej.", weaponSlotFilled = "Nie możesz użyć kolejnej broni typu %s!", equippedBag = "Nie możesz przemieszczać torby z wyekwipowanym przedmiotem!", equippedWeapon = "Nie możesz przemieszczać aktualnie wyekwipowanej broni!", nestedBags = "Nie możesz wrzucić torby do torby!", outfitAlreadyEquipped = "Już nosisz ubranie tego typu!", useTip = "Używa przedmiotu.", equipTip = "Zakłada przedmiot.", unequipTip = "Zdejmuje przedmiot.", consumables = "Towary konsumpcyjne.", plyNotValid = "Nie patrzysz na prawidłowego gracza.", restricted = "Zostałeś związany.", salary = "Otrzymałeś wynagrodzenie w wysokości %s", noRecog = "Nie poznajesz tej osoby.", curTime = "Aktualny czas to %s.", vendorEditor = "Edytor Sprzedawcy", edit = "Edytuj", disable = "Wyłącz", vendorPriceReq = "Wprowadź nową cenę dla tego produktu.", vendorEditCurStock = "Edytuj aktualny zapas", vendorStockReq = "Wprowadź ile produktów powinno się znajdować maksymalnie w zasobie", vendorStockCurReq = "Wprowadź ile przedmiotów jest dostępnych do sprzedarzy z całego zasobu.", you = "Ty", vendorSellScale = "Skala ceny sprzedaży", vendorNoTrade = "Nie możesz dokonać wymiany z tym sprzedawcą!", vendorNoMoney = "Sprzedawce nie stać na ten przedmiot!", vendorNoStock = "Sprzedawca nie ma tego produktu aktualnie w asortymencie!", contentTitle = "Nie znaleziono zawartości dla trybu Helix", contentWarning = "Zawartość dla trybu Helix nie został wgrana. Rezultatem tego może być brak części funkcji.\nCzy chciałbyś otworzyć stronę warsztatu z daną zawartością?", flags = "Flagi", mapRestarting = "Restart mapy za %d sekund!", chooseTip = "Wybierz postać do gry.", deleteTip = "Usuń tą postać.", storageInUse = "Ktoś inny używa tego teraz!", storageSearching = "Przeszukiwanie...", container = "Pojemnik", containerPassword = "Ustawiłeś hasło tego pojemnika na %s.", containerPasswordRemove = "Usunąłeś hasło z tego pojemnika.", containerPasswordWrite = "Wprowadź hasło.", containerName = "Ustawiłeś nazwę tego pojemnika na %s.", containerNameWrite = "Wprowadź nazwę.", containerNameRemove = "Usunąłeś nazwę z tego pojemnika.", containerInvalid = "Musisz patrzeć na pojemnik, aby tego użyć!", wrongPassword = "Wprowadziłeś złe hasło!", respawning = "Odradzanie...", tellAdmin = "Powiadom administrację o tym błędzie: %s", mapAdd = "Dodałeś nową scenerie mapy.", mapDel = "Usunąłęś %d scenerie mapy.", mapRepeat = "Teraz dodaj punkt drugorzędny", scoreboard = "Tabela", ping = "Ping: %d", viewProfile = "Obejrzyj profil Steam.", copySteamID = "Skopiuj Steam ID", money = "Pieniądze", moneyLeft = "Twoje Pieniądze: ", currentMoney = "Obecna ilość pieniędzy: ", invalidClass = "To nie jest odpowiednia klasa!", invalidClassFaction = "To nie jest poprawna klasa dla tej frakcji!", miscellaneous = "Różne", general = "Generalne", observer = "Obserwator", performance = "Wydajnośc", thirdperson = "Trzecia osoba", date = "Data", interaction = "Interakcja", server = "Serwer", resetDefault = "Ustaw domyślnie", resetDefaultDescription = "To zresetuje \"%s\" do swojej domyślej wartości \"%s\".", optOpenBags = "Otwórz torbe z ekwipunkiem", optdOpenBags = "Automatycznie pokazuje wszystkie torby w twoim ekwipunku gdy menu jest otwarte.", optShowIntro = "Pokaż intro przy wchodzeniu na serwer", optdShowIntro = "Pokazuje wstęp do Helixa następnym razem gdy będziesz wchodzić. Ta opcja jest zawsze wyłączona po tym gdy obejrzałeś wstęp.", optCheapBlur = "Wyłącz rozmazanie", optdCheapBlur = "Zastępuje rozmazanie interfejsu z zwykłym przyciemnieniem.", optObserverTeleportBack = "Przywraca cię do poprzedniej lokalizacji", optdObserverTeleportBack = "Przywraca cię do lokalizacji w której włączyłeś tryb obserwatora.", optObserverESP = "Pokaż ESP administracyjne", optdObserverESP = "Pokazuje nazwę i lokalizację każdego gracza na serwerze.", opt24hourTime = "Używaj czasu 24-godzinnego", optd24hourTime = "Pokazuj znacznik czasu w formacie 24-godzinnym, zamiast 12-godzinnego (AM/PM).", optChatNotices = "Pokazuj uwagi/ogłoszenia na czacie", optdChatNotices = "Przenosi wszystkie uwagi/ogłoszenia wyskakujące w prawym górnym rogu do czatu.", optChatTimestamps = "Show timestamps in chat", optdChatTimestamps = "Pokazuje godzinę wysłania przy każdej wiadomości na czacie.", optAlwaysShowBars = "Zawsze pokazuj tabelkę z informacjami", optdAlwaysShowBars = "Tworzy tabelkę z informacjami w lewym górnym rogu, bez znaczenia czy ma być ukryte czy nie.", optAltLower = "Ukryj ręce gdy są opuszczone.", optdAltLower = "Ukrywa ręce, gdy są opuszczone.", optThirdpersonEnabled = "Włącz trzecią osobe", optdThirdpersonEnabled = "Przenosi kamerę za ciebie. Również może być włączone w konsoli za pomocą \"ix_togglethirdperson\" ", optThirdpersonClassic = "Włącz klasyczną trzecią osobe", optdThirdpersonClassic = "Moves your character's view with your mouse.", optThirdpersonVertical = "Wysokość kamery", optdThirdpersonVertical = "Jak wysoko powinna być kamera.", optThirdpersonHorizontal = "Wyrównanie kamery", optdThirdpersonHorizontal = "Jak bardzo na lewo lub prawo powinna być kamera.", optThirdpersonDistance = "Odległość kamery", optdThirdpersonDistance = "Jak oddalona powinna być kamera.", optThirdpersonCrouchOffset = "Kamera na wysokości kucania", optdThirdpersonCrouchOffset = "Jak wysoko powinna być kamera podczas kucania.", optDisableAnimations = "Wyłącz animacje", optdDisableAnimations = "Zatrzymuje animację by zapewnić natychmiastowe przejście.", optAnimationScale = "Skala animacji", optdAnimationScale = "Jak bardziej szybko lub długo powinny być animacje.", optLanguage = "Język", optdLanguage = "Język interfejsu.", optMinimalTooltips = "Minimalne powiadomienia z HUD'u", optdMinimalTooltips = "Changes the HUD tooltip style to take up less space.", optNoticeDuration = "Długość powiadomienia", optdNoticeDuration = "Jak długo powinny wyświetlać się uwagi/ogłoszenia (w sekundach).", optNoticeMax = "Maksimum powiadomień", optdNoticeMax = "Ilość powiadomień zanim nadmiar zostanie usunięty.", optChatFontScale = "Rozmiar czcionki", optdChatFontScale = "Skalowanie czcionki na czacie.", optChatOutline = "Obrysuj tekst w czacie", optdChatOutline = " Obramowuje tekst czatu. Włącz to, jeśli masz problemy z czytaniem czatu.", cmdRoll = "Losuje liczbę pomiędzy 0 a wyznaczoną liczbą.", cmdPM = "Wysyła prywatną wiadomośc do kogoś.", cmdReply = "Wysyła prywatną wiadomośc do ostatniej osoby, od której otrzymałeś wiadomość.", cmdSetVoicemail = "Ustawia lub usuwa automatyczną odpowiedź gdy ktoś wysyła ci prywatną wiadomość.", cmdCharGiveFlag = "Daje wyznaczoną flagę(i) do danej osoby.", cmdCharTakeFlag = "Usuwa wyznaczoną flagę(i) osobie jeśli ją ma.", cmdToggleRaise = "Podnosi albo opuscza broń, którą trzymasz.", cmdCharSetModel = "Ustawia model postaci.", cmdCharSetSkin = "Ustawia skórkę dla danej postaci.", cmdCharSetBodygroup = "Ustawia bodygroupy dla danego modelu.", cmdCharSetAttribute = "Ustawia poziom danego atrybutu dla osoby.", cmdCharAddAttribute = "Dodaje poziom do danego atrybutu.", cmdCharSetName = "Zmienia nazwę na wyznaczoną nazwę.", cmdCharGiveItem = "Daje wyznaczony przedmiot osobie.", cmdCharKick = "Zmusza osobę do wyjścia z jej postaci.", cmdCharBan = "Zabrania osobie wchodzenia na danej postaci.", cmdCharUnban = "Unban na zbanowaną postać.", cmdGiveMoney = "Daje wyznaczoną ilość pieniędzy osobie, na którą patrzysz", cmdCharSetMoney = "Zmienia ilośc pieniędzy do wyznaczonej ilośći.", cmdDropMoney = "Wyrzuca wyznaczoną ilość pieniędzy przed tobą.", cmdPlyWhitelist = "Pozwala osobie na stworzenie postaci w wyznaczonej frakcji.", cmdCharGetUp = "Sprróbuj wstać po upadku.", cmdPlyUnwhitelist = "Usuwa osobie whiteliste z danej frakcji.", cmdCharFallOver = "Walisz salto na plecy", cmdBecomeClass = "Zostań daną klasą w obecnej frakcji.", cmdCharDesc = "Ustaw opis zewnętrzny postaci.", cmdCharDescTitle = "Opis zewnętrzny", cmdCharDescDescription = "Napisz opis zewnętrzny twojej postaci.", cmdPlyTransfer = "Przenosi osobę do wyznaczonej frakcji.", cmdCharSetClass = "Zmusza osobę do zostania wyznaczoną klasą w obecnej frakcji.", cmdMapRestart = "Restartuje mape po wyznaczonej ilości czasu.", cmdPanelAdd = "Dodaje Web Panel.", cmdPanelRemove = "Usuwa Web Panel na który patrzysz.", cmdTextAdd = "Dodaje napis.", cmdTextRemove = "Usuwa napis na który patrzysz.", cmdMapSceneAdd = "Dodaje punkt widokowy widoczny w menu postaci.", cmdMapSceneRemove = "Usuwa punkt widokowy widoczny w menu postaci.", cmdSpawnAdd = "Dodaje punkt odrodzenia danej frakcji.", cmdSpawnRemove = "Usuwa punkt odrodzenia na który patrzysz.", cmdAct = "Wykonuje animację %s .", cmdContainerSetPassword = "Ustawia hasło kontenera na który patrzysz.", cmdDoorSell = "Sprzedaje drzwi na które patrzysz.", cmdDoorBuy = "Kupuje drzwi na które patrzysz.", cmdDoorSetUnownable = "Ustawia drzwi na które patrzysz na niemożliwe do posiadania.", cmdDoorSetOwnable = "Ustawia drzwi na które patrzysz na możliwe do posiadania.", cmdDoorSetFaction = "Ustawia drzwi na które patrzysz na posiadane przed daną frakcję.", cmdDoorSetDisabled = "Zabrania wykonywania komend na drzwi na które patrzysz.", cmdDoorSetTitle = "Ustawia opis drzwi na które patrzysz.", cmdDoorSetParent = "Ustawia właściciela danych drzwi.", cmdDoorSetChild = "Ustawia podwłaścicieli danych drzwi.", cmdDoorRemoveChild = "Usuwa podwłaściciela danych drzwi.", cmdDoorSetHidden = "Ukrywa opis drzwi na które patrzysz. Wciąż są możliwe do posiadania.", cmdDoorSetClass = "Ustawia drzwi na które patrzysz na posiadane przez daną klasę.", cmdMe = "Wykonaj akcję fizyczną.", cmdIt = "Wykonaj akcję twojego otoczenia.", cmdW = "Szeptaj.", cmdY = "Krzycz.", cmdEvent = "Wykonuje akcję, którą każdy widzi.", cmdOOC = "Wysyła wiadomość na czacie out-of-character.", cmdLOOC = "Wysyła wiadomość na lokalnym czacie out-of-character." } ================================================ FILE: gamemode/languages/sh_portuguese.lua ================================================ NAME = "Portuguese" LANGUAGE = { helix = "Helix", introTextOne = "fist industries apresenta", introTextTwo = "em colaboração com %s", introContinue = "pressione espaço para continuar", helpIdle = "Selecione uma categoria", helpCommands = "Parametros de comando são são requiridos, enquanto [colchetes] são opcionais.", helpFlags = "As flags com um fundo verde são acessíveis por este personagem.", creditSpecial = "Agradecimentos especiais", creditLeadDeveloper = "Desenvolvedor Principal", creditUIDesigner = "Designer de interface do usuário", creditManager = "Gestor de projeto", creditTester = "Testador principal", chatTyping = "Escrevendo...", chatTalking = "Falando...", chatYelling = "Gritando...", chatWhispering = "Cochichando...", chatPerforming = "Atuando...", chatNewTab = "Nova aba...", chatReset = "Redefinir posição", chatResetTabs = "Redefinir abas", chatCustomize = "Customizar...", chatCloseTab = "Fechar aba", chatTabName = "Nome da aba", chatAllowedClasses = "Classe de bate-papo permitidas", chatTabExists = "Uma aba de chat com esse nome já existe!", chatMarkRead = "Marcar tudo como lido", community = "Comunidade", checkAll = "Selecionar tudo", uncheckAll = "Deselecionar tudo", color = "Cor", type = "Tipo", display = "Visualização", loading = "Carregando", dbError = "Falha na conexão com o banco de dados", unknown = "Desconhecido", noDesc = "Sem descrição disponível", create = "Criar", update = "Atualizar", load = "Carregar personagem", loadTitle = "Carregar um personagem", leave = "Sair", leaveTip = "Sair deste servidor.", ["return"] = "Retornar", returnTip = "Retornar para o menu anterior.", proceed = "Prosseguir", faction = "Facção", skills = "Habilidades", choose = "Escolher", chooseFaction = "Escolha uma facção", chooseDescription = "Descreva a aparência do seu personagem", chooseSkills = "Ajuste suas habilidades", name = "Nome", description = "Descrição", model = "Modelo", attributes = "Atributos", attribPointsLeft = "Pontos restantes", charInfo = "Character Information", charCreated = "Você criou seu personagem com sucesso.", charCreateTip = "Preencha os campos abaixo e pressione 'Finalizar' para criar o seu personagem.", invalid = "Você providênciou um %s inválido!", nameMinLen = "Seu nome deve conter ao menos %d caracteres!", nameMaxLen = "Seu nome não pode ser maior que %d caracteres!", descMinLen = "Sua descrição precisa conter ao menos %d caracteres!", maxCharacters = "Você não pode criar mais personagens!", player = "Jogador", finish = "Finalizar", finishTip = "Finalizar a criação do personagem.", needModel = "Você precisa escolher um modelo válido!", creating = "O seu personagem está sendo criado...", unknownError = "Um erro desconhecido ocorreu!", areYouSure = "Você tem certeza?", delete = "Deletar", deleteConfirm = "Este personagem será irrevogavelmente removido!", deleteComplete = "%s foi deletado.", no = "Não", yes = "Sim", close = "Fechar", save = "Salvar", itemInfo = "Nome: %s\nDescrição: %s", itemCreated = "Item(ns) criado com sucesso.", cloud_no_repo = "O repositório fornecido não é válido!", cloud_no_plugin = "O plugin providênciado não é válido!", inv = "Inventário", plugins = "Plugins", author = "Autor", version = "Versão", characters = "Personagens", business = "Négocios", settings = "Opções", config = "Configuração", chat = "Chat", appearance = "Aparência", misc = "Diversos", oocDelay = "Você precisa aguardar %s mais segundos antes de usar o OOC novamente!", loocDelay = "Você precisa aguardar %s mais segundos antes de usar o LOOC novamente!", usingChar = "Você já está utilizando este personagem.", notAllowed = "Você não tem permissão para fazer isso!", itemNoExist = "O item que você solicitou não existe!", cmdNoExist = "Esse comando não existe!", charNoExist = "Um personagem correspondente não foi encontrado!", plyNoExist = "Um jogador correspondente não foi encontrado!", cfgSet = "%s definiu \"%s\" para %s.", drop = "Soltar", dropTip = "Solta esse item do seu inventário.", take = "Pegar", takeTip = "Pega este item e coloca no seu inventário.", dTitle = "Porta sem proprietário", dTitleOwned = "Porta Comprada", dIsNotOwnable = "Esta porta não pode ser comprada.", dIsOwnable = "Você pode comprar esta porta pressionando F2.", dMadeUnownable = "Você tornou esta porta 'não comprável'.", dMadeOwnable = "Você tornou esta porta 'comprável'.", dNotAllowedToOwn = "Você não tem permissão para possuir esta porta!", dSetDisabled = "Você tornou esta porta 'desativada'.", dSetNotDisabled = "Você tornou esta porta 'ativada'.", dSetHidden = "Você tornou esta porta oculta.", dSetNotHidden = "Você tornou esta porta não mais oculta.", dSetParentDoor = "Você definiu esta porta como sua porta mãe.", dCanNotSetAsChild = "Você não pode definir a porta dos pais sendo criança!", dAddChildDoor = "Você adicionou essa porta como criança.", dRemoveChildren = "Você removeu todas as crianças desta porta.", dRemoveChildDoor = "Você removeu esta porta de ser uma criança.", dNoParentDoor = "Você não tem uma porta mãe estabelecida.", dOwnedBy = "Esta porta é de propriedade de %s.", dConfigName = "Portas", dSetFaction = "Esta porta agora pertence a facção %s.", dRemoveFaction = "Esta porta não pertence mais a nenhuma facção.", dNotValid = "Você não está olhando para nenhuma porta válida!", canNotAfford = "Você não pode pagar por isto!", dPurchased = "Você comprou essa porta por %s.", dSold = "Você vendeu esta porta por %s.", notOwner = "Você não é o dono disso!", invalidArg = "Você forneceu um valor inválido para o argumento #%s!", invalidFaction = "A facção que você forneceu não pôde ser encontrada!", flagGive = "%s atribuiu a %s as flags '%s'.", flagGiveTitle = "Dar Flags", flagTake = "%s revogou as flags '%s' de %s.", flagTakeTitle = "Revogar Flags", flagNoMatch = "Você precisa possuir as flags \"%s\" para realizar esta ação!", textAdded = "Você adicionou um texto.", textRemoved = "Você removeu o(s) texto(s) %s .", moneyTaken = "Você recebeu %s.", moneyGiven = "Você deu %s.", insufficientMoney = "Você não tem o suficiente para fazer isso!", businessPurchase = "Você comprou %s por %s.", businessSell = "Você vendeu %s por %s.", businessTooFast = "Por favor espere antes de comprar outro item!", cChangeModel = "%s mudou o modelo de %s para %s.", cChangeName = "%s mudou o nome de %s para %s.", cChangeSkin = "%s mudou a 'skin' de %s para %s.", cChangeGroups = "%s mudou o 'bodygroup' \"%s\" de %s para %s.", cChangeFaction = "%s mudou %s para a facção %s.", playerCharBelonging = "Este objeto pertençe ao seu outro personagem!", spawnAdd = "Você adiconou um ponto de ressurgimento para %s.", spawnDeleted = "Você removeu %s ponto(s) de ressurgimento.", someone = "Alguém", rgnLookingAt = "Permite que a pessoa que você está olhando reconheça você.", rgnWhisper = "Permita que te reconheça aqueles que te escutam sussurar.", rgnTalk = "Permite que aqueles que te escutam falando reconheça você.", rgnYell = "Permite que aqueles que te escutam gritando rechoneça você.", icFormat = "%s disse \"%s\"", rollFormat = "%s jogou os dados e tirou %s.", wFormat = "%s susurra \"%s\"", yFormat = "%s grita \"%s\"", sbOptions = "Clique para ver opções de %s.", spawnAdded = "Você adicionou um ponto de ressurgimento para %s.", whitelist = "%s adicionou %s à facção %s.", unwhitelist = "%s retirou %s da facção %s.", noWhitelist = "Você não tem permissão para usar este personagem!", gettingUp = "Você está se levantando...", wakingUp = "Você está retomando consciência...", Weapons = "Armas", checkout = "Ir ao Cheque (%s)", purchase = "Comprar", purchasing = "Comprando...", success = "Sucesso", buyFailed = "Falha na compra!", buyGood = "Compra sucesiva!", shipment = "Carga", shipmentDesc = "Esta carga pertence à %s.", class = "Classe", classes = "Classes", illegalAccess = "Acesso Ilegal.", becomeClassFail = "Você não pode se tornar um(a) %s!", becomeClass = "Você se tornou em um(a) %s.", setClass = "Você mudou a classe de %s para %s.", attributeSet = "Estabeleceu o atributo de %s de %s à %s.", attributeNotFound = "Você especificou um atributo inválido!", attributeUpdate = "You added %s's %s by %s.", noFit = "Você não tem espaço o suficiente para este item!", itemOwned = "You cannot interact with an item that you own on a different character!", help = "Ajuda", commands = "Comandos", doorSettings = "Propiedades da Porta", sell = "Vender", access = "Acessar", locking = "Trancando esta entidade...", unlocking = "Destrancando esta entidade...", modelNoSeq = "Seu modelo não suporta esta ação!", notNow = "Você não tem permissão para fazer isto agora!", faceWall = "Você precisa encarar uma parede para fazer isto!", faceWallBack = "Você precisa estar contra a parede para fazer isto!", descChanged = "Você mudou a descrição do seu personagem.", noOwner = "O dono é inválido!", invalidItem = "Você especificou um item inválido!", invalidInventory = "Você especificou um inventário inválido!", home = "Início", charKick = "%s expulsou o personagem %s.", charBan = "%s baniu o personagem %s.", charBanned = "Este personagem está banido.", playerConnected = "%s conectou-se ao servidor.", playerDisconnected = "%s desconectou-se do servidor.", setMoney = "Você ajustou o dinheiro de %s para %s.", itemPriceInfo = "Você pode comprar este item por %s.\nVocê pode vender este item por %s", free = "Grátis", vendorNoSellItems = "Não há items para vender.", vendorNoBuyItems = "Não há items para comprar.", vendorSettings = "Propiedades do Fornecedor", vendorUseMoney = "Fornecedor deve usar dinheiro?", vendorNoBubble = "Esconder o balão do vendedor?", mode = "Modo", price = "Preço", stock = "Estoque", none = "Nenhum", vendorBoth = "Comprar e Vender", vendorBuy = "Apenas Comprar", vendorSell = "Apenas Vender", maxStock = "Estoque Máximo", vendorFaction = "Editar Facção", buy = "Comprar", vendorWelcome = "Bem-vindo, o que posso lhe oferecer hoje?", vendorBye = "Até mais!", charSearching = "Você já está procurando um outro personagem!", charUnBan = "%s desbaniu o personagem %s.", charNotBanned = "Este personagem não está banido!", quickSettings = "Configurações Rápidas", vmSet = "Você ajustou seu correio de voz.", vmRem = "Você removeu seu correio de voz.", noPerm = "Você não é permitido a fazer isto!", youreDead = "Você está Morto.", injMajor = "Parece críticamente lesionado", injLittle = "Parece lesionado", chgName = "Mudar Nome", chgNameDesc = "Insira o nome do personagem abaixo.", weaponSlotFilled = "Você não pode usar outra arma de %s!", equippedBag = "Você não pode mover uma bolsa que tem um item equipado!", equippedWeapon = "Você não pode mover uma arma que está sendo equipada!", nestedBags = "Você não pode colocar um inventário dentro de outro!", outfitAlreadyEquipped = "Você já está vestindo este tipo de roupa!", useTip = "Usa este item.", equip = "Equipar", equipTip = "Equipa o item..", unequip = "Desequipar", unequipTip = "Desequipa o item.", consumables = "Consumíveis", plyNotValid = "Você não está olhando para um jogador válido!", restricted = "Você foi preso.", salary = "Você recebeu %s do seu salário.", noRecog = "Você não reconhece esta pessoa.", curTime = "A hora é %s.", vendorEditor = "Editar Fornecedor", edit = "Editar", disable = "Desativar", vendorPriceReq = "Insira um novo preço para este item.", vendorEditCurStock = "Edite o Estoque Atual", vendorStockReq = "Insira a quantidade Máxima de Estoque o item deve ter.", vendorStockCurReq = "Insira quantos itens estão disponíveis para compra do estoque máximo.", you = "Você", vendorSellScale = "Escala do preço de venda", vendorNoTrade = "Você não consegue trocar com este fornecedor!", vendorNoMoney = "Este fornecedor não consegue pagar este item!", vendorNoStock = "Este fornecedor não possui esse item no estoque!", contentTitle = "Faltando Conteúdo do Helix", contentWarning = "Você não tem o conteúdo do Helix instalado. Isto pode resultar na falta de alguns aspectos.\nVocê gostaria de abrir a página da Workshop para baixar o conteúdo?", flags = "Flags", mapRestarting = "O mapa irá reiniciar em %d segundos!", chooseTip = "Escolha este personagem para jogar.", deleteTip = "Delete este personagem.", storageInUse = "Alguém já está usando isto!", storageSearching = "Procurando...", container = "Container", containerPassword = "Você ajustou a senha deste container para %s.", containerPasswordRemove = "Você retirou a senha deste container.", containerPasswordWrite = "Insira a senha.", containerName = "Você mudou o nome deste container para %s.", containerNameWrite = "Insira o nome.", containerNameRemove = "Você removeu o nome deste container.", containerInvalid = "Você precisa estar olhando para um container para fazer isto!", wrongPassword = "Você inseriu uma senha errada!", respawning = "Ressurgindo...", tellAdmin = "Informe um membro da staff sobre este erro: %s", scoreboard = "Scoreboard", ping = "Ping: %d", viewProfile = "Ver Perfil da Steam", copySteamID = "Copiar Steam ID", money = "Dinheiro", moneyLeft = "Seu Dinheiro: ", currentMoney = "Dinheiro Restante: ", invalidClass = "Essa não é uma classe válida!", invalidClassFaction = "Essa não é uma classe válida para esta facção!", miscellaneous = "Diversos", general = "Geral", observer = "Observador", performance = "Performance", thirdperson = "Terceira Pessoa", date = "Data", interaction = "Interação", server = "Servidor", resetDefault = "Reiniciar para o padrão", resetDefaultDescription = "Isto irá reiniciar \"%s\" ao seu valor padrão de \"%s\".", optOpenBags = "Abrir bolsa com inventário", optdOpenBags = "Automáticamente vê todas as bolsas em seu inventário quando o menu é aberto.", optShowIntro = "Mostrar intro ao entrar", optdShowIntro = "Mostra a introdução do Helix na próxima vez que entrar. Isto é sempre desativado depois que já assiste.", optCheapBlur = "Desativar embaçamento", optdCheapBlur = "Substitui o embaçamento da UI com uma ofuscação simples.", optObserverTeleportBack = "Retornar ao último local", optdObserverTeleportBack = "Retorna você ao local que você usou o modo observador..", optObserverESP = "Mostrar admin ESP", optdObserverESP = "Mostra o nome e o local de cada jogador no servidor.", opt24hourTime = "Usar horário de 24 horas.", optd24hourTime = "Mostra horários em formato de 24 horas, ao em vez de 12 (AM/PM).", optChatNotices = "Mostrar noticias no chat", optdChatNotices = "Coloca todas as notícias que aparecem no topo da tela no chat.", optChatTimestamps = "Mostrar horários no chat", optdChatTimestamps = "Acopla o horário à cada mensagem enviada no chat.", optAlwaysShowBars = "Sempre mostrar barras de informação", optdAlwaysShowBars = "Mostra as informações de barra no topo esquerdo da tela, mesmo se estiverem escondidas.", optAltLower = "Esconder mãos quando abaixadas", -- @todo remove me optdAltLower = "Esconde suas mãos quando elas estão ao lado.", -- @todo remove me optThirdpersonEnabled = "Ativar terceira pessoa", optdThirdpersonEnabled = "Move a câmera para atrás de você. Também pode ser ativo com o comando \"ix_togglethirdperson\".", optThirdpersonClassic = "Ativar terceira pessoa clássico", optdThirdpersonClassic = "Move a visão do seu personagem com o mouse.", optThirdpersonVertical = "Altura da câmera", optdThirdpersonVertical = "O quão alto a câmera deve estar.", optThirdpersonHorizontal = "Posição da câmera", optdThirdpersonHorizontal = "O quão para a direita ou esquerda a câmera deve estar.", optThirdpersonDistance = "Distância da câmera", optdThirdpersonDistance = "O quão longe a câmera deve estar.", optThirdpersonCrouchOffset = "Altura da câmera agachada.", optdThirdpersonCrouchOffset = "O quão alto a câmera deve estar enquanto agachado.", optDisableAnimations = "Desativar animações", optdDisableAnimations = "Desativa as animações do menu do Helix para transições instantâneas..", optAnimationScale = "Escala da Animação", optdAnimationScale = "O quão rápido ou devagar as animações devem estar.", optLanguage = "Língua", optdLanguage = "A língua mostrada na UI do Helix.", optMinimalTooltips = "Dicas minimalistas no HUD", optdMinimalTooltips = "Muda o estilo de dicas no HUD para tomar menos espaço.", optNoticeDuration = "Duração de noticia", optdNoticeDuration = "Por quanto tempo mostrar noticias (em segundos).", optNoticeMax = "Máximo de noticias", optdNoticeMax = "O número máximo de noticias até que noticias passadas sejam removidas.", optChatFontScale = "Tamanho da fonte do Chat", optdChatFontScale = "O quão grande ou pequeno a fonte deve ser.", optChatOutline = "Borda no texto do Chat", optdChatOutline = "Desenha uma borda ao redor do texto mostrado no Chat. Ative caso não consiga ver o texto.", cmdRoll = "Rola um número entre 0 e o número especificado.", cmdPM = "Manda uma mensagem privada para alguém.", cmdReply = "Manda uma mensagem privada para a última pessoa que lhe mandou uma mensagem.", cmdSetVoicemail = "Ajuste ou remova a mensagem de resposta automática quando alguém lhe manda uma mensagem.", cmdCharGiveFlag = "Dá a(s) flag(s) especificadas para alguém.", cmdCharTakeFlag = "Remove a(s) flag(s) especificadas de alguém caso tenham.", cmdToggleRaise = "Levanta ou abaixa a arma que está segurando.", cmdCharSetModel = "Ajusta o modelo do personagem da pessoa.", cmdCharSetSkin = "Ajusta a skin para o modelo do personagem da pessoa.", cmdCharSetBodygroup = "Ajusta o bodygroup para o modelo do personagem da pessoa.", cmdCharSetAttribute = "Ajusta o nível do atributo especificado para a pessoa.", cmdCharAddAttribute = "Adiciona um nível ao atributo especificado para a pessoa.", cmdCharSetName = "Muda o nome de alguém para o nome especificado.", cmdCharGiveItem = "Dá o item especificado para alguém.", cmdCharKick = "Faz alguém sair do personagem que está jogando forçadamente.", cmdCharBan = "Proibe alguém de entrar no servidor com o personagem atual.", cmdCharUnban = "Permite que um personagem que foi banido possa ser usado novamente.", cmdGiveMoney = "Dá uma quantia de dinheiro especificada á pessoa que está olhando.", cmdCharSetMoney = "Muda a quantia total de dinheiro de alguém para a quantia especificada.", cmdDropMoney = "Deixa cair a quantia especificada de dinheiro em uma caixinha à sua frente.", cmdPlyWhitelist = "Permite que alguém crie um personagem na facção especificada.", cmdCharGetUp = "Tenta se levantar após ter caído.", cmdPlyUnwhitelist = "Tira a permissão de alguém para criar um personagem na facção especificada.", cmdCharFallOver = "Faz você cair.", cmdBecomeClass = "Tenta se tornar parte da classe especificada na sua facção.", cmdCharDesc = "Ajusta a tua descrição física.", cmdCharDescTitle = "Descrição Física", cmdCharDescDescription = "Insira a descrição física de teu personagem.", cmdPlyTransfer = "Transfere alguém para a facção designada.", cmdCharSetClass = "Força alguém a se tornar a classe especificada na facção da pessoa.", cmdMapRestart = "Reinicia o mapa após um certo intervalo de tempo.", cmdPanelAdd = "Coloca um painel da Web no mundo.", cmdPanelRemove = "Remove o painel que está a olhar.", cmdTextAdd = "Adiciona um bloco de texto no mundo.", cmdTextRemove = "Remove o bloco de texto que está a olhar.", cmdMapSceneAdd = "Adiciona uma câmera cinemática que pode ser vista do menu.", cmdMapSceneRemove = "Removes a camera viewpoint that is shown in the character menu.", cmdSpawnAdd = "Adiciona um ponto de ressurgimento para a facção especificada.", cmdSpawnRemove = "Remove qualquer ponto de ressurgimento que esteja a olhar.", cmdAct = "Faz a animação de %s", cmdContainerSetPassword = "Ajuste a senha do container que está a olhar.", cmdDoorSell = "Venda a porta que está a olhar.", cmdDoorBuy = "Compra a porta que está a olhar.", cmdDoorSetUnownable = "Faz a porta que está a olhar incomprável.", cmdDoorSetOwnable = "Faz a porta que está a olhar comprável.", cmdDoorSetFaction = "Designa a porta que está a olhar à uma facção especificada.", cmdDoorSetDisabled = "Faz com que seja impossível realizar comandos na porta que está a olhar.", cmdDoorSetTitle = "Coloca um título na porta que está a olhar.", cmdDoorSetParent = "Define o parentesco de um par de portas.", cmdDoorSetChild = "Define o herdeiro de um par de portas.", cmdDoorRemoveChild = "Remove o herdeiro de um par de portas.", cmdDoorSetHidden = "Esconde a descrição da porta que está a olhar, mas ainda permite que alguém a compre.", cmdDoorSetClass = "Permite que a porta que está a olhar seja propiedade da classe especificada.", cmdMe = "Faça uma ação física.", cmdIt = "Faça algo ao seu redor realizar uma ação.", cmdW = "Susurre algo para as pessoas próximas a você.", cmdY = "Grite algo para as pessoas ao seu redor.", cmdEvent = "Faça algo realizar uma ação que todos podem ver.", cmdOOC = "Mande uma mensagem para o chat fora-de-personagem global.", cmdLOOC = "Mande uma mensagem para o chat dentro-de-personagem local." } ================================================ FILE: gamemode/languages/sh_russian.lua ================================================ --Maybe this thing have some trouble with translate NAME = "Русский" LANGUAGE = { helix = "Helix", introTextOne = "Fist Industries представляет", introTextTwo = "В сотрудничестве с %s", introContinue = "Чтобы продолжить, нажмите Пробел", helpIdle = "Выберите категорию", helpCommands = "Параметры команды отмеченные <стрелками> обязательны, тогда как [скобки] опциональны.", helpFlags = "Флаги с зеленым фоном обозначают доступные этому персонажу флаги.", creditSpecial = "Особая благодарность", creditLeadDeveloper = "Ведущий разработчик", creditUIDesigner = "Дизайнер интерфейса", creditManager = "Менеджер проекта", creditTester = "Ведущий тестировщик", chatTyping = "Пишет...", chatTalking = "Говорит...", chatYelling = "Кричит...", chatWhispering = "Шепчет...", chatPerforming = "Выполняет действие...", chatNewTab = "Новая вкладка...", chatReset = "Сбросить позицию", chatResetTabs = "Сбросить позицию вкладок", chatCustomize = "Настроить...", chatCloseTab = "Закрыть вкладку", chatTabName = "Имя вкладки", chatNewTabTitle = "Новая Вкладка", chatAllowedClasses = "Разрешенные классы чата", chatTabExists = "Вкладка чата с таким именем уже существует!", chatMarkRead = "Отметить как прочитанные", community = "Сообщество", checkAll = "Отметить все", uncheckAll = "Убрать все", color = "Цвет", type = "Тип", display = "Изображение", loading = "Загрузка", dbError = "Сбой подключения к базе данных", unknown = "Неизвестный", noDesc = "Нет описания", create = "Создать", update = "Обновить", load = "Загрузить персонажа", loadTitle = "Загрузить персонажа.", leave = "Выйти с сервера", leaveTip = "Покинуть сервер.", ["return"] = "Вернуться", returnTip = "Вернуться в предыдущее меню.", proceed = "Продолжить", faction = "Фракция", skills = "Навыки", choose = "Выбрать", chooseFaction = "Выберите фракцию", chooseDescription = "Опишите свою внешность", chooseSkills = "Выберите ваши навыки", name = "Имя", description = "Описание", model = "Модель", attributes = "Атрибуты", attribPointsLeft = "Очки", Endurance = "Выносливость", Stamina = "Быстроходность", Strength = "Сила", charInfo = "Информация о персонаже", charCreated = "Персонаж успешно создан.", charCreateTip = "Заполните поля ниже и нажмите 'Создать' чтобы создать персонажа.", invalid = "Вы ввели недействительный %s!", nameMinLen = "Имя не должно быть короче %d символов!", nameMaxLen = "Имя не должно быть длиннее %d символов!", descMinLen = "Описание не должно быть короче %d символов!", maxCharacters = "Достигнут лимит созданных персонажей!", player = "Игрок", finish = "Создать", finishTip = "Закончить создание персонажа.", needModel = "Выберите одну из моделей!", creating = "Персонаж создается...", unknownError = "Произошла неизвестная ошибка!", areYouSure = "Вы уверены?", delete = "Удалить", deleteConfirm = "Этот персонаж будет безвозвратно удален!", deleteComplete = "%s был удален.", no = "Нет", yes = "Да", close = "Закрыть", save = "Сохранить", itemInfo = "Имя: %s\nОписание: %s", itemCreated = "Предмет(ы) успешно создан(ы).", cloud_no_repo = "Данный репозиторий недействителен!", cloud_no_plugin = "Данный плагин недействителен!", inv = "Инвентарь", plugins = "Плагины", author = "Автор", version = "Версия", characters = "Меню персонажей", business = "Бизнес", settings = "Настройки", config = "Конфигурация", chat = "Чат", appearance = "Описание", misc = "Остальное", oocDelay = "Подождите %s секунд чтобы снова написать в OOC чат!", loocDelay = "Подождите %s секунд чтобы снова написать в LOOC чат!", usingChar = "Вы уже используете этого персонажа.", notAllowed = "Эта команда вам недоступна!", itemNoExist = "Запрошенный предмет не существует!", cmdNoExist = "Такой команды не существует!", charNoExist = "Такой персонаж не найден!", plyNoExist = "Такой игрок не найден!", cfgSet = "%s установил с \"%s\" на %s.", drop = "Выбросить", dropTip = "Выбросить предмет из инвентаря.", take = "Взять", takeTip = "Взять этот предмет в инвентарь.", dTitle = "Дверь", dTitleOwned = "Купленная дверь", dIsNotOwnable = "Эта дверь не имеет владельца.", dIsOwnable = "Вы можете приобрести эту дверь, нажав F2.", dMadeUnownable = "Вы сделали эту дверь непокупаемой.", dMadeOwnable = "Вы сделали эту дверь приобретаемой.", dNotAllowedToOwn = "Вы не можете владеть этой дверью!", dSetDisabled = "Вы заблокировали данную дверь.", dSetNotDisabled = "Вы разблокировали данную дверь.", dSetHidden = "Вы скрыли описание данной двери.", dSetNotHidden = "Вы раскрыли описание данной двери.", dSetParentDoor = "Вы сделали эту дверь родительской.", dCanNotSetAsChild = "Вы не можете сделать родительскую дверь дочерней!", dAddChildDoor = "Вы сделали эту дверь дочерней.", dRemoveChildren = "Вы удалили у этой двери ее дочерние двери.", dRemoveChildDoor = "Вы удалили дочернюю связь у этой двери.", dNoParentDoor = "У вас нет родительской двери для связи.", dOwnedBy = "Эта дверь принадлежит %s.", dConfigName = "Двери", dSetFaction = "Эта дверь теперь принадлежит фракции %s.", dRemoveFaction = "Эта дверь больше не принадлежит ни одной фракции.", dNotValid = "Вы не смотрите на валидную дверь!", canNotAfford = "Вы не можете позволить это себе!", dPurchased = "Вы купили эту дверь за %s.", dSold = "Вы продали эту дверь за %s.", notOwner = "Вы не владелец этой двери!", invalidArg = "Указано недопустимое значение для аргумента #%s!", invalidFaction = "Указанная фракция не найдена!", flagGive = "%s выдал игроку %s флаги'%s'.", flagGiveTitle = "Выдать флаги", flagTake = "%s забрал у игрока '%s' флаги %s.", flagTakeTitle = "Забрать флаги", flagNoMatch = "Вы должны иметь \"%s\" флаш(и) чтобы сделать это!", textAdded = "Текст добавлен.", textRemoved = "Вы удалили %s текст(ов).", moneyTaken = "Вы получили %s.", moneyGiven = "Вы отдали %s.", insufficientMoney = "У вас не хватает денег, чтобы сделать это!", businessPurchase = "Вы купили %s за %s.", businessSell = "Вы продали %s за %s.", businessTooFast = "Подождите, прежде чем покупать другой товар!", cChangeModel = "%s сменил %s модель на %s.", cChangeName = "%s сменил %s имя на %s.", cChangeSkin = "%s сменил %s скин на %s.", cChangeGroups = "%s сменил %s \"%s\" на бодигруппу %s.", cChangeFaction = "%s перенес персонажа %s во фракцию %s ", playerCharBelonging = "Этот предмет принадлежит другому вашему персонажу!", spawnAdd = "Вы добавили спаун для фракции%s.", spawnDeleted = "Вы удалили %s спаун(ов).", someone = "Кто-то", rgnLookingAt = "Представиться человеку, на которого вы смотрите.", rgnWhisper = "Представиться людям в радиусе шепота.", rgnTalk = "Представиться людям в радиусе разговора.", rgnYell = "Представиться людям в радиусе крика.", icFormat = "%s говорит \"%s\"", rollFormat = "%s получил число %s из %s.", wFormat = "%s шепчет \"%s\"", yFormat = "%s кричит \"%s\"", sbOptions = "Нажмите, чтобы посмотреть настройки %s.", spawnAdded = "Вы добавили спаун для %s.", whitelist = "%s выдал вайтлист %s для фракции %s.", unwhitelist = "%s забрал вайтлист у %s фракции %s.", noWhitelist = "У вас нет вайтлиста для этого персонажа!", gettingUp = "Вы поднимаетесь...", wakingUp = "Вы приходите в себя...", Ammunition = "Боеприпасы", Storage = "Вместилища", Outfit = "Одежда", Weapons = "Оружие", checkout = "Оформить заказ (%s)", purchase = "Купить", purchasing = "Покупка...", success = "Успешно", buyFailed = "Покупка не выполнена!", buyGood = "Покупка выполнена!", shipment = "Товар", shipmentDesc = "Этот товар принадлежит %s.", class = "Класс", classes = "Классы", illegalAccess = "Незаконный доступ.", becomeClassFail = "Вы не можете выбрать класс %s!", becomeClass = "Вы выбрали класс %s.", setClass = "Вы установили игроку %s класс %s.", attributeSet = "Вы сменили игроку %s %s на %s.", attributeNotFound = "Вы указали неверный атрибут!", attributeUpdate = "Вы добавили игроку %s %s, %s единицы.", noFit = "Недостаточно места для этого предмета!", itemOwned = "Вы не можете взаимодействовать с предметом, который принадлежит другому вашему персонажу!", help = "Помощь", commands = "Команды", doorSettings = "Настройки двери", sell = "Продать", access = "Доступ", locking = "Обьект блокируется...", unlocking = "Обьект разблокируется...", modelNoSeq = "Ваша модель не поддерживает эту анимацию.", notNow = "Вы не можете сделать это сейчас.", faceWall = "Прислонитесь лицом к стене, чтобы выполнить эту анимацию.", faceWallBack = "Прислонитесь спиной к стене, чтобы выполнить эту анимацию.", descChanged = "Описание вашего персонажа успешно изменено.", noOwner = "Неверный владелец!", invalidItem = "Вы указали неверный предмет!", invalidInventory = "Вы указали неверный инвентарь!", home = "Домой", charKick = "%s кикнул персонажа %s.", charBan = "%s забанил персонажа %s.", charBanned = "Этот персонаж забанен.", charBannedTemp = "Этот персонаж временно забанен.", playerConnected = "%s подключился к серверу.", playerDisconnected = "%s отключился от сервера.", setMoney = "Вы установили игроку %s баланс %s единиц.", itemPriceInfo = "Вы можете приобрести этот предмет за %s.\nВы можете продать этот предмет за %s", free = "Бесплатно", vendorNoSellItems = "У торговца нету предметов для продажи.", vendorNoBuyItems = "Нету предметов для покупки.", vendorSettings = "Настройки торговца", vendorUseMoney = "Торговец использует деньги?", vendorNoBubble = "Убрать пузырек над головой?", mode = "Режим", price = "Цена", stock = "Кол-во", none = "Нет", vendorBoth = "Покупать и продавать", vendorBuy = "Только покупать", vendorSell = "Только продавать", maxStock = "Максимальное кол-во", vendorFaction = "Доступ фракций", buy = "Купить", vendorWelcome = "Приветствую вас в моем магазине. Желаете что-то купить?", vendorBye = "Приходите еще!", charSearching = "Вы уже ищите другого персонажа!", charUnBan = "%s разбанил персонажа %s.", charNotBanned = "Этот персонаж не забанен!", quickSettings = "Быстрые настройки", vmSet = "Вы настроили свою голосовую почту.", vmRem = "Вы удалили свою голосовую почту.", noPerm = "У вас нет прав чтобы это сделать!", youreDead = "Вы погибли", injMajor = "Очень сильно ранен", injLittle = "Слегка ранен", chgName = "Сменить имя", chgNameDesc = "Введите новое имя персонажа.", weaponSlotFilled = "Слот %s занять другим оружием!", equippedBag = "Вы не можете переместить сумку с предметами внутри!", equippedWeapon = "Вы не можете манипулировать экипированным оружием!", nestedBags = "Вы не можете положить сумку с вещами в хранилище!", outfitAlreadyEquipped = "Вы уже носите этот элемент экипировки!", Load = "Зарядить", useTip = "Использовать этот предмет.", View = "Просмотреть", Equip = "Экипировать", equipTip = "Экипировать этот предмет.", Unequip = "Стянуть", unequipTip = "Стянуть этот предмет.", consumables = "Расходные материалы", plyNotValid = "Вы должны смотреть на существующего игрока!", restricted = "Вы были связаны.", salary = "Вы получили свою зарплату в размере %s.", noRecog = "Вы не знаете этого персонажа.", curTime = "Время: %s.", vendorEditor = "Редактор торговца", edit = "Изменить макс. кол-во", disable = "Отключить", vendorPriceReq = "Введите новую цену для этого предмета.", vendorEditCurStock = "Изменить настоящее кол-во", vendorStockReq = "Введите максимальное количество предметов.", vendorStockCurReq = "Введите доступное количество предметов для покупки.", you = "Персонаж", vendorSellScale = "Множитель продажи", vendorNoTrade = "Вы не можете торговать с этим торговцем!", vendorNoMoney = "У торговца недостаточно денег для покупки!", vendorNoStock = "У торговца нету этого товара!", contentTitle = "Отсутствует контент Helix", contentWarning = "У вас нет контента фреймворка Helix. Могут отсутствовать некоторые функции.\nХотите открыть страницу контента в Steam Workshop?", flags = "Флаги", mapRestarting = "Рестарт карты произойдет через %d секунд!", chooseTip = "Выберите персонажа, чтобы начать играть.", deleteTip = "Удалить этого персонажа.", storageInUse = "Кто-то уже использует это!", storageSearching = "Открываю...", container = "Контейнер", containerPassword = "На контейнер установлен замок с паролем %s.", containerPasswordRemove = "Пароль с этого контейнера был убран.", containerPasswordWrite = "Введите пароль.", containerName = "Имя контейнера установлено %s.", containerNameWrite = "Введите имя.", containerNameRemove = "Имя контейнера убрано.", containerInvalid = "Вы должны смотреть на контейнер, чтобы это выполнить это!", wrongPassword = "Неверный пароль!", respawning = "Респавнимся...", tellAdmin = "Сообщите администрации эту ошибку: %s", mapAdd = "Вы добавили сцену карты.", mapDel = "Вы удалили сцену(ы) %d.", mapRepeat = "Теперь добавьте вторую точку.", scoreboard = "Игроки", ping = "Пинг: %d", viewProfile = "Открыть профиль Steam", copySteamID = "Скопировать Steam ID", money = "Наличные", moneyLeft = "Ваши наличные: ", currentMoney = "Баланс: ", invalidClass = "Недопустимый класс!", invalidClassFaction = "Недопустимый класс или фракция!", miscellaneous = "Разное", general = "Общие", observer = "ESP и наблюдение", performance = "Производительность", thirdperson = "Третье лицо", date = "Дата", interaction = "Взаимодействие", server = "Сервер", resetDefault = "По умолчанию", resetDefaultDescription = "Это сбросит значение \"%s\" до стандартного значения \"%s\".", optOpenBags = "Открывать сумки в инвентаре ", optdOpenBags = "При открытии меню, автоматически открывает все сумки в вашем инвентаре.", optShowIntro = "Интро при подключении", optdShowIntro = "Показывает интро фреймворка при следующем подключении. Автоматически отключается после просмотра.", optCheapBlur = "Отключить блюр", optdCheapBlur = "Заменяет блюр в интерфейсе на простое затемнение.", optObserverTeleportBack = "Возвращение на место", optdObserverTeleportBack = "Возвращает вас к месту, в котором вы вошли в режим наблюдения.", optObserverESP = "Админ ESP", optdObserverESP = "Показывает имена и местоположение каждого игрока на сервере.", opt24hourTime = "24-часовой формат времени", optd24hourTime = "Показывать время в 24-х часовом формате, вместо 12 часового (AM/PM).", optChatNotices = "Уведомления в чате", optdChatNotices = "Выводит все уведомления которые появляются в правом верхнем углу в текстовый чат.", optChatTimestamps = "Время в чате", optdChatTimestamps = "Отмечает время отправки кажого сообщения в чате.", optAlwaysShowBars = "Всегда показывать информационную панель", optdAlwaysShowBars = "Отображает состояние персонажа в левом верхнем углу на постоянной основе.", optAltLower = "Опуская, скрывать руки", -- @todo remove me optdAltLower = "Скрывать руки, при опускани оных.", -- @todo remove me optThirdpersonEnabled = "|Третье лицо", optdThirdpersonEnabled = "Перемещение камеры позади вас. Также переключается консольной командой \'ix_togglethirdperson\'", optThirdpersonClassic = "|Классическая камера", optdThirdpersonClassic = "Управлять взглядом вашего персонажа с помощью мыши.", optThirdpersonVertical = "Высота камеры", optdThirdpersonVertical = "Высота расположения камеры от 3 лица.", optThirdpersonHorizontal = "Камера по горизонтали", optdThirdpersonHorizontal = "Расположение камеры влево или вправо от персонажа.", optThirdpersonDistance = "Расстояние камеры", optdThirdpersonDistance = "Как далеко располагается камера от персонажа.", optThirdpersonCrouchOffset = "Высота камеры при присяде", optdThirdpersonCrouchOffset = "Высота расположения камеры от 3 лица.", optDisableAnimations = "Отключить анимации интерфейса", optdDisableAnimations = "Отключает анимации интерфейса, для резкого перехода между окнами.", optAnimationScale = "Скорость анимаций", optdAnimationScale = "Насколько быстрее или медленнее проигрывается анимация.", optLanguage = "Язык", optdLanguage = "Язык интерфейса Helix.", optMinimalTooltips = "Минималистичные подсказки", optdMinimalTooltips = "Изменяет стиль показа подсказок, чтобы занимать меньше места.", optNoticeDuration = "Длительность уведомления", optdNoticeDuration = "Как долго показываются уведомления (в секундах).", optNoticeMax = "Максимальное кол-во уведомлений", optdNoticeMax = "Количество уведомлений, показываемых одновременно.", optChatFontScale = "Шрифт чата", optdChatFontScale = "Насколько больше или меньше шрифт чата должен быть.", optChatOutline = "Контур в чате", optdChatOutline = "Рисует контур вокруг текста в чате. Включите, если у вас проблемы с чтением чата.", cmdRoll = "Случайное число от 0 до указанного значения.", cmdPM = "Отправить личное сообщение выбранному персонажу.", cmdReply = "Отправить личное сообщение последнему человеку, от которого вы получили сообщение.", cmdSetVoicemail = "Установить или убрать сообщение автоответчика, при отправке вам сообщения.", cmdCharGiveFlag = "Дать указанному игроку флаг(и)", cmdCharTakeFlag = "Забрать указанный(е) флаг(и) у игрока, если такие имеются.", cmdToggleRaise = "Поднять или опустить оружие, которое вы удерживаете.", cmdCharSetModel = "Установить выбранную модель игроку.", cmdCharSetSkin = "Установить выбранный скин для модели игрока.", cmdCharSetBodygroup = "Установить бодигруппу для модели игрока.", cmdCharSetAttribute = "Установить уровень выбранного атрибута у игрока.", cmdCharAddAttribute = "Добавить к уровню выбранное количество очков.", cmdCharSetName = "Изменить имя пользователя на заданное.", cmdCharGiveItem = "Дать заданный предмет игроку.", cmdCharKick = "Кикнуть выбранного персонажа.", cmdCharBan = "Ограничить игроку доступ к выбранному персонажу.", cmdCharUnban = "Снимает ограничение на игру за выбранного персонажа", cmdGiveMoney = "Передать сумму денег персонажу, на которого вы смотрите.", cmdCharSetMoney = "Изменить сумму денег у персонажа на указанную.", cmdDropMoney = "Положить определенную сумму денег в хранилище на которое вы смотрите.", cmdPlyWhitelist = "Выдать доступ игроку к созданию персонажа в указанной фракции", cmdCharGetUp = "Попытаться встать после падения.", cmdPlyUnwhitelist = "Забрать доступ у игрока к созданию персонажа в указанной фракции.", cmdCharFallOver = "Расслабиться и упасть на землю.", cmdBecomeClass = "Взять выбранный класс в вашей текущей фракции.", cmdCharDesc = "Установить физическое описание вашего персонажа.", cmdCharDescTitle = "Физическое описание", cmdCharDescDescription = "Введите физическое описание персонажа.", cmdPlyTransfer = "Перевести выбранного персонажа в указанную фракцию.", cmdCharSetClass = "Сменить выбранному игроку указанный класс в его фракции.", cmdMapRestart = "Перезапустить карту через указанное время.", cmdPanelAdd = "Создать веб изображение там, куда направлен взгляд.", cmdPanelRemove = "Удалить веб изображение, на которое вы смотрите.", cmdTextAdd = "Создать блок текста в мире.", cmdTextRemove = "Удалить блоки текста, на которые вы смотрите.", cmdMapSceneAdd = "Добавить кинематографичную камеру, в меню выбора персонажа.", cmdMapSceneRemove = "Убрать кинематографичную камеру, в меню выбора персонажа.", cmdSpawnAdd = "Добавить точку спавна для указанной фракции.", cmdSpawnRemove = "Удалить все точки спавна, на которые вы смотрите.", cmdAct = "Выполнить анимацию %s.", cmdContainerSetPassword = "Установить пароль для контейнера, на который вы смотрите.", cmdDoorSell = "Продать дверь, на которую вы смотрите.", cmdDoorBuy = "Купить дверь, на которую вы смотрите.", cmdDoorSetUnownable = "Сделать дверь, на которую вы смотрите, недоступной для покупки.", cmdDoorSetOwnable = "Сделать дверь, на которую вы смотрите, доступной для приобретения.", cmdDoorSetFaction = "Установить двери, на которую вы смотрите, принадлежность к фракции.", cmdDoorSetDisabled = "Отключить возможность взаимодействия с дверью, на которую вы смотрите.", cmdDoorSetTitle = "Установить заголовок двери, на которую вы смотрите.", cmdDoorSetParent = "Выбрать родительскую дверь для дочерних дверей.", cmdDoorSetChild = "Выбрать дочерние двери для родительской двери.", cmdDoorRemoveChild = "Убрать у двери дочернюю зависимость.", cmdDoorSetHidden = "Убирает описание двери на которую вы смотрите, но оставляет возможность владения.", cmdDoorSetClass = "Установить двери, на которую вы смотрите, принадлежность к классу во фракции.", cmdMe = "Выполнить физическое действие.", cmdIt = "Совершить какое-либо действие вокруг себя.", cmdW = "Прошептать что-то людям, стоящим рядом.", cmdY = "Крикнуть что-то людям, стоящим в большом радиусе.", cmdEvent = "Выполнить действие, которое увидят все игроки.", cmdOOC = "Отправить сообщение в глобальный out-of-character чат .", cmdLOOC = "Отправить сообщение в локальный out-of-character чат.", Instructions = "Информация" } ================================================ FILE: gamemode/languages/sh_spanish.lua ================================================ -- SPANISH TRANSLATION -- Cuboxis (http://steamcommunity.com/id/Cuboxis) -- Geferon (https://steamcommunity.com/id/GEFERON) -- Whitehole (https://steamcommunity.com/id/whitehole) -- Carlos Bes NAME = "Español" LANGUAGE = { helix = "Helix", introTextOne = "fist industries presenta", introTextTwo = "en colaboración con %s", introContinue = "pulsa espacio para continuar", helpIdle = "Selecciona una categoría", helpCommands = "Los parámetros de comando rodeados de son obligatorios, mientras que los rodeados de [corchetes] son opcionales.", helpFlags = "Las Flags con el fondo verde son accesibles por este personaje.", creditSpecial = "Muchas Gracias", creditLeadDeveloper = "Desarrollador Principal", creditUIDesigner = "Diseñador de la Interfaz de Usuario", creditManager = "Jefe de Proyecto", creditTester = "Tester Principal", chatTyping = "Escribiendo...", chatTalking = "Hablando...", chatYelling = "Gritando...", chatWhispering = "Susurrando...", chatPerforming = "Actuando...", chatNewTab = "Nueva pestaña...", chatReset = "Restablecer posición", chatResetTabs = "Restablecer pestañas", chatCustomize = "Personalizar...", chatCloseTab = "Cerrar Pestaña", chatTabName = "Nombre de Pestaña", chatNewTabTitle = "Nueva Pestaña", chatAllowedClasses = "Categorías de chat permitidas", chatTabExists = "¡Ya existe una pestaña de chat con ese nombre!", chatMarkRead = "Marcar todo como leído", community = "Comunidad", checkAll = "Seleccionar todo", uncheckAll = "Deseleccionar todo", color = "Color", type = "Tipo", display = "Visualización", loading = "Cargando", dbError = "Fallo de conexión con la Base de Datos", unknown = "Desconocido", noDesc = "Descripción no disponible", create = "Crear", update = "Actualizar", load = "Cargar personaje", loadTitle = "Cargar un Personaje", leave = "Abandonar", leaveTip = "Salir del servidor.", ["return"] = "Volver", returnTip = "Vuelve al menú anterior.", proceed = "Continuar", faction = "Facción", skills = "Habilidades", choose = "Escoger", chooseFaction = "Escoge una Facción", chooseDescription = "Describe la apariencia de tu personaje", chooseSkills = "Ajusta tus habilidades", name = "Nombre", description = "Descripción", model = "Modelo", attributes = "Atributos", attribPointsLeft = "Puntos restantes", charInfo = "Información del personaje", charCreated = "Has creado tu personaje correctamente.", charCreateTip = "Completa todos los campos obligatorios y haz clic en 'Finalizar' para crear a tu personaje.", invalid = "Has introducido un(a) %s inválido/a.", nameMinLen = "Tu nombre debe de tener al menos %d caracteres.", nameMaxLen = "Tu nombre no puede tener mas de %d caracteres.", descMinLen = "Tu descripción debe tener al menos %d caracteres.", maxCharacters = "¡No puedes crear mas personajes!", player = "Jugador", finish = "Finalizar", finishTip = "Finaliza la creación de personaje.", needModel = "Necesitas escoger un modelo válido.", creating = "Tu personaje está siendo creado...", unknownError = "Ha ocurrido un error desconocido", areYouSure = "¿Estás seguro?", delete = "Borrar", deleteConfirm = "¡Este personaje será eliminado PERMANENTEMENTE!", deleteComplete = "%s ha sido eliminado.", no = "No", yes = "Sí", close = "Cerrar", save = "Guardar", itemInfo = "Nombre: %s\nDescripción: %s", itemCreated = "Objeto(s) creado(s) correctamente.", cloud_no_repo = "El repositorio introducido no es válido.", cloud_no_plugin = "El plugin introducido no es válido.", inv = "Inventario", plugins = "Plugins", pluginLoaded = "%s ha habilitado el plugin \"%s\" y se habilitará con el siguiente cambio de mapa.", pluginUnloaded = "%s ha deshabilitado el plugin \"%s\" y se deshabilitará con el siguiente cambio de mapa.", loadedPlugins = "Plugins Habilitados", unloadedPlugins = "Plugins Deshabilitados", on = "On", off = "Off", author = "Autor", version = "Versión", characters = "Personajes", business = "Negocios", settings = "Opciones", config = "Configuración", chat = "Chat", appearance = "Apariencia", misc = "Misceláneo", oocDelay = "Debes esperar %s segundo(s) antes de usar de nuevo el chat OOC.", loocDelay = "Debes esperar %s segundo(s) antes de usar de nuevo el chat LOOC.", usingChar = "Ya estás usando éste personaje.", notAllowed = "Lo siento, no tienes permitido hacer esto.", itemNoExist = "Lo siento, el objeto especificado no existe.", cmdNoExist = "Lo siento, este comando no existe.", charNoExist = "Lo siento, el personaje especificado no ha podido ser encontrado.", plyNoExist = "Lo siento, el jugador correspondiente no ha podido ser encontrado.", cfgSet = "%s ha establecido \"%s\" a %s.", drop = "Tirar", dropTip = "Suelta el objeto de tu inventario.", take = "Coger", takeTip = "Pone este objeto en tu inventario.", dTitle = "Puerta sin propietario", dTitleOwned = "Puerta comprada", dIsNotOwnable = "Esta puerta no puede tener propietario.", dIsOwnable = "Puedes comprar esta puerta pulsando F2.", dMadeUnownable = "Has hecho que esta puerta no pueda tener propietario.", dMadeOwnable = "Has hecho que esta puerta pueda tener propietario.", dNotAllowedToOwn = "No tienes permiso para hacer esta puerta de tu propiedad.", dSetDisabled = "Has deshabilitado esta puerta.", dSetNotDisabled = "Has habilitado esta puerta.", dSetHidden = "Has hecho que esta puerta esté oculta.", dSetNotHidden = "Has hecho que esta puerta no esté oculta.", dSetParentDoor = "Has establecido esta puerta como puerta primaria.", dCanNotSetAsChild = "No puedes establecer la puerta primaria como puerta secundaria.", dAddChildDoor = "Has añadido esta puerta como puerta secundaria.", dRemoveChildren = "Has eliminado todas las puertas secundarias de esta puerta.", dRemoveChildDoor = "Has eliminado esta puerta como puerta secundaria.", dNoParentDoor = "No tienes seleccionada una puerta primaria.", dOwnedBy = "Esta puerta es propiedad de %s.", dConfigName = "Puertas", dSetFaction = "Esta puerta ahora pertenece a %s.", dRemoveFaction = "Esta puerta ya no pertenece a ninguna facción.", dNotValid = "No estás mirando a una puerta válida.", canNotAfford = "No puedes permitirte comprar esto.", dPurchased = "Has comprado esta puerta por %s.", dSold = "Has vendido esta puerta por %s.", notOwner = "Esto no te pertenece.", invalidArg = "El valor introducido para el argumento #%s es inválido.", invalidFaction = "La facción que has introducido no se ha podido encontrar.", flagGive = "%s ha dado a %s las flags '%s'.", flagGiveTitle = "Dar Flags", flagTake = "%s ha quitado las flags '%s' de %s.", flagTakeTitle = "Quitar Flags", flagNoMatch = "Debes tener la(s) flag(s) \"%s\" para hacer esta acción.", panelAdded = "Has añadido un panel.", panelRemoved = "Has borrado %d panel(es).", textAdded = "Has añadido un texto.", textRemoved = "Has borrado %s texto(s).", moneyTaken = "Has recibido %s.", moneyGiven = "Has dado %s.", insufficientMoney = "No puedes permitirte esto con tu dinero.", belowMinMoneyDrop = "No puedes soltar menos de %s.", businessPurchase = "Has comprado %s por %s.", businessSell = "Has vendido %s por %s.", businessTooFast = "Por favor, espera antes de comprar otro objeto.", cChangeModel = "%s ha cambiado el modelo de %s a %s.", cChangeName = "%s ha cambiado el nombre de %s a %s.", cChangeSkin = "%s ha cambiado el 'skin' de %s a %s.", cChangeGroups = "%s ha cambiado el 'bodygroup' \"%s\" de %s a %s.", cChangeFaction = "%s ha transferido a %s a la facción %s.", playerCharBelonging = "Este objeto ya pertenece a otro de tus personajes.", spawnAdd = "Has añadido un punto de reaparición para %s.", spawnDeleted = "Has eliminado un punto de reaparición de %s.", someone = "Alguien", rgnLookingAt = "Permite que te reconozca la persona a la que estás mirando.", rgnWhisper = "Permite que te reconozcan aquellos que te escuchen susurrar.", rgnTalk = "Permite que te reconozcan aquellos que te escuchen hablar.", rgnYell = "Permite que te reconozcan aquellos que te escuchen gritar.", icFormat = "%s dice \"%s\"", rollFormat = "%s ha tirado los dados y ha sacado un %s.", wFormat = "%s susurra \"%s\"", yFormat = "%s grita \"%s\"", sbOptions = "Haz clic para ver las opciones de %s.", spawnAdded = "Has añadido punto de aparición de %s.", whitelist = "%s ha añadido a %s en la lista blanca de la facción %s.", unwhitelist = "%s ha eliminado a %s de la lista blanca de la facción %s.", noWhitelist = "No tienes la lista blanca para la facción de este personaje.", charNotWhitelisted = "%s no está en la lista blanca para la facción %s.", gettingUp = "Te estás levantando...", wakingUp = "Estás recuperando la consciencia...", Weapons = "Armas", checkout = "Ir a la cesta (%s)", purchase = "Comprar", purchasing = "Comprando...", success = "Éxito.", buyFailed = "Compra fallida.", buyGood = "Compra exitosa!", shipment = "Envío", shipmentDesc = "Este envío pertenece a %s.", class = "Clase", classes = "Clases", illegalAccess = "Acceso ilegal.", becomeClassFail = "Fallo en convertirse en %s.", becomeClass = "Te has convertido en %s.", setClass = "Has establecido la clase de %s a %s.", attributeSet = "Has establecido el nivel de %s de %s a %s puntos.", attributeNotFound = "Has especificado un atributo inválido.", attributeUpdate = "Has añadido al nivel de %s de %s %s puntos.", noFit = "Este objeto no cabe en tu inventario.", itemOwned = "No puedes interactuar con un objeto de tus otros personajes.", help = "Ayuda", commands = "Comandos", doorSettings = "Configuración de puertas", sell = "Vender", access = "Acceso", locking = "Bloqueando esta entidad...", unlocking = "Desbloqueando esta entidad...", modelNoSeq = "Tu modelo no es compatible con este acto.", notNow = "No tienes permiso para hacer esto ahora.", faceWall = "Debes estar mirando una pared para hacer esto.", faceWallBack = "Tu espalda tiene que estar mirando una pared para hacer esto.", descChanged = "Has cambiado la descripción de tu personaje.", noOwner = "El propietario no es válido.", invalidItem = "El objeto no es válido.", invalidInventory = "El inventario no es válido.", home = "Inicio", charKick = "%s ha echado a %s.", charBan = "%s ha bloqueado el personaje %s.", charBanned = "Este personaje está bloqueado.", charBannedTemp = "Este personaje está temporalmente bloqueado.", playerConnected = "%s se ha conectado al servidor.", playerDisconnected = "%s se ha desconectado del servidor.", setMoney = "Has establecido el dinero de %s a %s.", itemPriceInfo = "Puedes comprar este objeto por %s.\nPuedes vender este objeto por %s.", free = "Gratis", vendorNoSellItems = "No hay objetos para vender.", vendorNoBuyItems = "No hay objetos para comprar.", vendorSettings = "Configuración del Vendedor", vendorUseMoney = "¿El vendedor debe usar dinero?", vendorNoBubble = "¿Esconder burbuja del vendedor?", category = "Categoría", mode = "Modo", price = "Precio", stock = "Existencias", none = "Nada", vendorBoth = "Comprar y vender", vendorBuy = "Sólo comprar", vendorSell = "Sólo vender", maxStock = "Existencias máximas", vendorFaction = "Editor de facción", buy = "Comprar", vendorWelcome = "Bienvenido/a a mi tienda, ¿Qué puedo hacer por usted?", vendorBye = "¡Vuelva pronto!", charSearching = "Ya te encuentras buscando a otro personaje, por favor espera.", charUnBan = "%s ha desbloqueado el personaje %s.", charNotBanned = "Este personaje no está bloqueado.", quickSettings = "Configuración rápida", vmSet = "Has establecido tu buzón de voz.", vmRem = "Has eliminado tu buzón de voz.", noPerm = "No tienes permiso para hacer esto.", youreDead = "Estás muerto.", injMajor = "Parece que está gravemente herido.", injLittle = "Parece que está herido.", chgName = "Cambiar nombre", chgNameDesc = "Introduce abajo el nuevo nombre del personaje.", weaponSlotFilled = "No puedes usar otra arma %s!", equippedBag = "La bolsa que has movido tiene un objeto que te has equipado.", equippedWeapon = "¡No puedes mover un arma que tienes equipada actualmente!", nestedBags = "¡No puedes poner un inventario dentro de un inventario de almacenamiento!", outfitAlreadyEquipped = "¡Ya estas usando este tipo de ropa!", useTip = "Usa el objeto.", equip = "Equipar", equipTip = "Equipa el objeto.", unequip = "Desequipar", unequipTip = "Desequipa el objeto.", consumables = "Consumibles", plyNotValid = "No estás mirando a un jugador válido.", restricted = "Has sido atado.", salary = "Has recibido %s de tu salario.", noRecog = "No reconoces a esta persona.", curTime = "La hora actual es %s.", vendorEditor = "Editor de vendedor", edit = "Editar", disable = "Deshabilitar", vendorPriceReq = "Introduce el nuevo precio del artículo.", vendorEditCurStock = "Editar las existencias actuales", vendorStockReq = "Introduce la cantidad máxima de existencias que debe tener el artículo.", vendorStockCurReq = "Introduce cuantos artículos de las existencias máximas estan disponibles para comprar.", you = "Tú", vendorSellScale = "Escala de precio de venta", vendorNoTrade = "¡No puedes intercambiar con este vendedor!", vendorNoMoney = "Este vendedor no puede permitirse comprar ese artículo.", vendorNoStock = "Este vendedor no tiene ese artículo en existencia.", vendorMaxStock = "¡Este vendedor ya tiene las existencias máximas de ese artículo!", contentTitle = "Falta el Contenido de Helix", contentWarning = "No tienes el contenido de Helix montado. Puede que falten ciertas características.\n¿Quieres abrir la página de Workshop del contenido de Helix?", flags = "Flags", mapRestarting = "¡El mapa se reiniciará en %d segundos!", chooseTip = "Escoge este personaje para jugar.", deleteTip = "Eliminar este personaje.", storageInUse = "¡Alguien ya está usando esto!", storageSearching = "Buscando...", container = "Contenedor", containerPassword = "Has establecido la contraseña de este contenedor a %s.", containerPasswordRemove = "Has eliminado la contraseña de este contenedor.", containerPasswordWrite = "Introduce la contraseña.", containerName = "Has cambiado el nombre de este contenedor a %s.", containerNameWrite = "Introduce el nombre.", containerNameRemove = "Has borrado el nombre de este contenedor.", containerInvalid = "¡Necesitas estar mirando a un contenedor para hacer esto!", passwordAttemptLimit = "¡Demasiados intentos fallidos de contraseña!", wrongPassword = "Has introducido una contraseña errónea.", respawning = "Reapareciendo...", syntax = "Formato: %s", tellAdmin = "Informa a un miembro del staff de este error: %s", mapAdd = "Has añadido una escena de mapa.", mapDel = "Has borrado %d escena(s) de mapa.", mapRepeat = "Ahora añade el segundo punto.", scoreboard = "Marcador", ping = "Ping: %d", viewProfile = "Ver el perfil de Steam.", copySteamID = "Copiar Steam ID", money = "Dinero", moneyLeft = "Dinero restante: ", currentMoney = "Dinero actual: ", invalidClass = "¡Esa no es una clase válida!", invalidClassFaction = "¡Esa no es una clase válida para la facción!", miscellaneous = "Misceláneo", general = "General", observer = "Observador", performance = "Rendimiento", thirdperson = "Tercera Persona", date = "Fecha", interaction = "Interacción", server = "Servidor", resetDefault = "Restablecer a valores predeterminados", resetDefaultDescription = "Esto restablecerá \"%s\" a su valor predeterminado de \"%s\".", optOpenBags = "Abrir objetos con inventario", optdOpenBags = "Abrir automáticamente todos los objetos con inventario cuando el menú es abierto.", optShowIntro = "Mostrar la intro al entrar", optdShowIntro = "Muestra la introducción de Helix la siguiente vez que entres. Esta opción siempre es deshabilitada una vez la has visto.", optCheapBlur = "Deshabilitar difuminación", optdCheapBlur = "Remplaza la difuminación de la interfaz con un oscurecido simple.", optObserverTeleportBack = "Volver a la posición anterior", optdObserverTeleportBack = "Te hace volver a la posición en la cual te encontrabas antes de entrar en modo observador.", optObserverESP = "Mostrar admin ESP", optdObserverESP = "Muestra los nombres y las localizaciones de cada jugador en el servidor.", opt24hourTime = "Usar formato de 24 horas", optd24hourTime = "Muestra las marcas de tiempo en formato 24 horas, en vez de usar el formato de 12 horas (AM/PM).", optChatNotices = "Mostrar avisos en el chat", optdChatNotices = "Pone todos los avisos que aparecen en la esquina superior derecha, en el chat.", optChatTimestamps = "Mostrar marcas de tiempo en el chat", optdChatTimestamps = "Prepone una marca temporal a cada mensaje en el chat.", optAlwaysShowBars = "Mostrar siempre las barras de información", optdAlwaysShowBars = "Siempre muestra las barras de información en la esquina superior izquierda, sin importar si deberían de mostrarse o no.", optAltLower = "Ocultar las manos al estar bajadas", -- @todo remove me optdAltLower = "Oculta tus manos cuando están bajadas.", -- @todo remove me optThirdpersonEnabled = "Activar Tercera Persona", optdThirdpersonEnabled = "Pone la cámara detrás de ti. Esto también puede ser activado con el comando de consola \"ix_togglethirdperson\".", optThirdpersonClassic = "Activar el estilo clásico de tercera persona", optdThirdpersonClassic = "Mueve la vista de tu personaje con tu ratón.", optThirdpersonVertical = "Altura de Tercera Persona", optdThirdpersonVertical = "Como de alto debería de estar la cámara de tercera persona.", optThirdpersonHorizontal = "Horizontal de Tercera Persona", optdThirdpersonHorizontal = "Como de lejos a la izquierda o la derecha debería de estar la cámara.", optThirdpersonDistance = "Distancia de Tercera Persona", optdThirdpersonDistance = "Como de lejos debería de estar la cámara.", optThirdpersonCrouchOffset = "Altura de la cámara al agacharse", optdThirdpersonCrouchOffset = "A que altura debe de estar la cámara cuando se está agachado.", optDisableAnimations = "Desactivar animaciones", optdDisableAnimations = "Elimina las animaciones de la interfaz de usuario, haciendo que las transiciones sean instantáneas.", optAnimationScale = "Escala de animación", optdAnimationScale = "Velocidad de reproducción de las animaciones de la interfaz de usuario.", optLanguage = "Idioma", optdLanguage = "El idioma mostrado en la interfaz de usuario de Helix.", optMinimalTooltips = "Información del HUD minimalista", optdMinimalTooltips = "Cambia el estilo de la información del HUD para que ocupe menos espacio.", optNoticeDuration = "Duración de Avisos", optdNoticeDuration = "Cuanto tiempo duran los avisos (en segundos).", optNoticeMax = "Máximo de Avisos", optdNoticeMax = "La cantidad de avisos mostrados antes de que sean eliminados los anteriores.", optChatFontScale = "Tamaño de fuente del chat", optdChatFontScale = "Modifica el tamaño de la fuente del chat.", optChatOutline = "Contorno en el texto de chat", optdChatOutline = "Dibuja un contorno alrededor del texto del chat, en vez de una sombra paralela. Activa esto si tienes problemas leyendo el texto.", optEscCloseMenu = "Tecla ESC vuelve al juego", optdEscCloseMenu = "Al pulsar la tecla ESC, se cierra el menú de Helix y también se oculta automáticamente el menú de pausa de Garry's Mod. Es decir, vuelves directamente a controlar tu personaje.", cmdRoll = "Tira un numero entre 0 y el número especificado.", cmdPM = "Envía un mensaje privado a alguien.", cmdReply = "Responde a la ultima persona de la cual recibiste un mensaje privado.", cmdSetVoicemail = "Establece o elimina el mensaje de respuesta automática cuando alguien te envía un mensaje privado.", cmdCharGiveFlag = "Da la(s) flag(s) especificadas a alguien.", cmdCharTakeFlag = "Elimina la(s) flag(s) especificada(s) de alguien si las tienen.", cmdToggleRaise = "Levanta o baja el arma que estas que estas sosteniendo.", cmdCharSetModel = "Establece el modelo del personaje de una persona.", cmdCharSetSkin = "Establece la 'skin' del modelo de un personaje.", cmdCharSetBodygroup = "Establece el 'bodygroup' especificado del modelo de un personaje.", cmdCharSetAttribute = "Establece el nivel del atributo especificado para alguien.", cmdCharAddAttribute = "Añade un nivel al atributo especificado para alguien.", cmdCharSetName = "Cambia el nombre de un personaje al nombre especificado.", cmdCharGiveItem = "Da el objeto especificado a alguien.", cmdCharKick = "Saca al jugador de su personaje, devolviéndolo a la pantalla de selección.", cmdCharBan = "Bloquea a un jugador el uso del personaje especificado.", cmdCharUnban = "Desbloquea un personaje para que el jugador pueda usarlo otra vez.", cmdGiveMoney = "Da una cantidad específica de dinero a la persona a la que estas mirando.", cmdCharSetMoney = "Cambia la cantidad total de dinero de alguien a la cantidad especificada.", cmdDropMoney = "Tira una cantidad específica de dinero en una pequeña caja en frente de ti.", cmdPlyWhitelist = "Permite el acceso de alguien a la facción especificada (lista blanca).", cmdCharGetUp = "Intenta levantarte después de haber caído.", cmdPlyUnwhitelist = "Elimina el acceso de alguien a la facción especificada.", cmdCharFallOver = "Hace que tus rodillas se debiliten y te caigas.", cmdBecomeClass = "Intenta formar parte de la clase especificada en tu facción actual.", cmdCharDesc = "Establece tu descripción física.", cmdCharDescTitle = "Descripción Física", cmdCharDescDescription = "Introduce la descripción física de tu personaje.", cmdPlyTransfer = "Transfiere a alguien a la facción especificada.", cmdCharSetClass = "Forzar a alguien formar parte de la clase especificada de su facción.", cmdMapRestart = "Reinicia el mapa después de la cantidad de tiempo especificada.", cmdPanelAdd = "Pone un panel web en el mundo.", cmdPanelRemove = "Elimina el panel web al que estas mirando.", cmdTextAdd = "Pone un bloque de texto en el mundo.", cmdTextRemove = "Elimina bloques de texto de donde estas mirando.", cmdMapSceneAdd = "Añade un punto de cámara cinemática la cual es mostrada en el menú de selección de personaje.", cmdMapSceneRemove = "Elimina un punto de cámara que es mostrado en el menú de selección de personaje.", cmdSpawnAdd = "Añade un punto de aparición para la facción especificada.", cmdSpawnRemove = "Elimina cualquier punto de aparición a los cuales estés mirando.", cmdAct = "Realiza la animación %s.", cmdContainerSetPassword = "Establece la contraseña para el contenedor que estas mirando.", cmdDoorSell = "Vende la puerta a la que estas mirando.", cmdDoorBuy = "Compra la puerta a la que estas mirando.", cmdDoorSetUnownable = "Hace que la puerta a la que estas mirando no pueda tener dueño.", cmdDoorSetOwnable = "Hace que la puerta a la que estas mirando pueda tener dueño.", cmdDoorSetFaction = "Hace que la puerta a la que estas mirando pertenezca a una facción.", cmdDoorSetDisabled = "No permite que ningún comando sea ejecutado en la puerta a la cual estas mirando.", cmdDoorSetTitle = "Establece el titulo de la puerta a la cual estas mirando.", cmdDoorSetParent = "Selecciona la puerta como madre de un grupo de puertas.", cmdDoorSetChild = "Establece la puerta como hija de un grupo de puertas.", cmdDoorRemoveChild = "Elimina la puerta como hija del grupo de puertas seleccionado.", cmdDoorSetHidden = "Oculta la descripción de la puerta a la cual estas mirando, pero sigue permitiendo que pueda tener dueño.", cmdDoorSetClass = "Hace que la puerta a la que estas mirando pertenezca la clase especificada de una facción.", cmdMe = "Describe una acción de tu personaje en tercera persona.", cmdIt = "Describe un evento a tu alrededor.", cmdW = "Susurra algo a las personas a tu alrededor.", cmdY = "Grita algo a las personas a tu alrededor.", cmdEvent = "Describe un evento que todos pueden ver en el servidor.", cmdOOC = "Envía un mensaje en el chat (global) fuera de personaje.", cmdLOOC = "Envía un mensaje en el chat (local) fuera de personaje.", iconEditorAlignBest = "Alineación óptima", iconEditorWidth = "Ancho", iconEditorHeight = "Altura", iconEditorCopy = "Copiar al portapapeles", iconEditorCopied = "Datos del modelo del objeto copiados al portapapeles.", iconEditorAlignFront = "Alinear desde el frente", iconEditorAlignAbove = "Alinear desde arriba", iconEditorAlignRight = "Alinear desde la derecha", iconEditorAlignCenter = "Alinear desde el centro" } ================================================ FILE: gamemode/shared.lua ================================================ --- Top-level library containing all Helix libraries. A large majority of the framework is split into respective libraries that -- reside within `ix`. -- @module ix --- A table of variable types that are used throughout the framework. It represents types as a table with the keys being the -- name of the type, and the values being some number value. **You should never directly use these number values!** Using the -- values from this table will ensure backwards compatibility if the values in this table change. -- -- This table also contains the numerical values of the types as keys. This means that if you need to check if a type exists, or -- if you need to get the name of a type, you can do a table lookup with a numerical value. Note that special types are not -- included since they are not real types that can be compared with. -- @table ix.type -- @realm shared -- @field string A regular string. In the case of `ix.command.Add`, this represents one word. -- @field text A regular string. In the case of `ix.command.Add`, this represents all words concatenated into a string. -- @field number Any number. -- @field player Any player that matches the given query string in `ix.util.FindPlayer`. -- @field steamid A string that matches the Steam ID format of `STEAM_X:X:XXXXXXXX`. -- @field character Any player's character that matches the given query string in `ix.util.FindPlayer`. -- @field bool A string representation of a bool - `false` and `0` will return `false`, anything else will return `true`. -- @field color A color represented by its red/green/blue/alpha values. -- @field vector A 3D vector represented by its x/y/z values. -- @field optional This is a special type that can be bitwise OR'd with any other type to make it optional. Currently only -- supported in `ix.command.Add`. -- @field array This is a special type that can be bitwise OR'd with any other type to make it an array of that type. Currently -- only supported in `ix.option.Add`. -- @see ix.command.Add -- @see ix.option.Add -- @usage -- checking if type exists -- print(ix.type[2] != nil) -- > true -- -- -- getting name of type -- print(ix.type[ix.type.string]) -- > "string" ix.type = ix.type or {} -- Define gamemode information. GM.Name = "Helix" GM.Author = "nebulous.cloud" GM.Website = "https://nebulous.cloud" GM.Version = "β" do -- luacheck: globals player_manager player_manager.ixTranslateModel = player_manager.ixTranslateModel or player_manager.TranslateToPlayerModelName function player_manager.TranslateToPlayerModelName(model) model = model:lower():gsub("\\", "/") local result = player_manager.ixTranslateModel(model) if (result == "kleiner" and !model:find("kleiner")) then local model2 = model:gsub("models/", "models/player/") result = player_manager.ixTranslateModel(model2) if (result != "kleiner") then return result end model2 = model:gsub("models/humans", "models/player") result = player_manager.ixTranslateModel(model2) if (result != "kleiner") then return result end model2 = model:gsub("models/zombie/", "models/player/zombie_") result = player_manager.ixTranslateModel(model2) if (result != "kleiner") then return result end end return result end end -- Include core framework files. ix.util.Include("core/cl_skin.lua") ix.util.IncludeDir("core/libs/thirdparty") ix.util.Include("core/sh_config.lua") ix.util.IncludeDir("core/libs") ix.util.IncludeDir("core/derma") ix.util.IncludeDir("core/hooks") -- Include language and default base items. ix.lang.LoadFromDir("helix/gamemode/languages") ix.item.LoadFromDir("helix/gamemode/items") -- Called after the gamemode has loaded. function GM:Initialize() -- Load all of the Helix plugins. ix.plugin.Initialize() -- Restore client options ix.option.Load() -- Restore the configurations from earlier if applicable. ix.config.Load() end -- luacheck: globals IX_RELOADED IX_RELOADED = false -- Called when a file has been modified. function GM:OnReloaded() -- Reload the default fonts. if (CLIENT) then hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) -- Reload the scoreboard. if (IsValid(ix.gui.scoreboard)) then ix.gui.scoreboard:Remove() end else -- Auto-reload support for faction pay timers. for index, faction in ipairs(ix.faction.indices) do for _, v in ipairs(team.GetPlayers(index)) do if (faction.pay and faction.pay > 0) then timer.Adjust("ixSalary"..v:SteamID64(), faction.payTime or 300, 0) else timer.Remove("ixSalary"..v:SteamID64()) end end end end if (!IX_RELOADED) then IX_RELOADED = true -- Load all of the Helix plugins. ix.plugin.Initialize() -- Restore the configurations from earlier if applicable. ix.config.Load() -- Restore client options ix.option.Load() end end -- Include default Helix chat commands. ix.util.Include("core/sh_commands.lua") if (SERVER and game.IsDedicated()) then concommand.Remove("gm_save") concommand.Add("gm_save", function(client, command, arguments) end) concommand.Add("gmod_admin_cleanup", function(client, command, arguments) end) end -- add entries for c_viewmodels that aren't set by default player_manager.AddValidModel("group02male01", "models/humans/group02/male_01.mdl") player_manager.AddValidHands("group02male01", "models/weapons/c_arms_citizen.mdl", 1, "0000000") player_manager.AddValidModel("group02male03", "models/humans/group02/male_03.mdl") player_manager.AddValidHands("group02male03", "models/weapons/c_arms_citizen.mdl", 1, "0000000") player_manager.AddValidModel("group01female07", "models/player/group01/female_07.mdl") player_manager.AddValidHands("group01female07", "models/weapons/c_arms_citizen.mdl", 1, "0000000") player_manager.AddValidModel("group02female03", "models/player/group01/female_03.mdl") player_manager.AddValidHands("group02female03", "models/weapons/c_arms_citizen.mdl", 1, "0000000") ================================================ FILE: helix.example.yml ================================================ database: adapter: "sqlite" hostname: "example.com" username: "example" password: "example" database: "helix" port: 3306 ================================================ FILE: helix.txt ================================================ "helix" { "base" "sandbox" "title" "Helix" "author" "nebulous.cloud" } ================================================ FILE: plugins/3dpanel.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "3D Panels" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds web panels that can be placed on the map." -- List of available panel dislays. PLUGIN.list = PLUGIN.list or {} if (SERVER) then util.AddNetworkString("ixPanelList") util.AddNetworkString("ixPanelAdd") util.AddNetworkString("ixPanelRemove") -- Called when the player is sending client info. function PLUGIN:PlayerInitialSpawn(client) -- Send the list of panel displays. timer.Simple(1, function() if (IsValid(client)) then local json = util.TableToJSON(self.list) local compressed = util.Compress(json) local length = compressed:len() net.Start("ixPanelList") net.WriteUInt(length, 32) net.WriteData(compressed, length) net.Send(client) end end) end -- Adds a panel to the list, sends it to the players, and saves data. function PLUGIN:AddPanel(position, angles, url, scale, brightness) scale = math.Clamp((scale or 1) * 0.1, 0.001, 5) brightness = math.Clamp(math.Round((brightness or 100) * 2.55), 1, 255) -- Find an ID for this panel within the list. local index = #self.list + 1 -- Add the panel to the list so it can be sent and saved. self.list[index] = {position, angles, nil, nil, scale, url, nil, brightness} -- Send the panel information to the players. net.Start("ixPanelAdd") net.WriteUInt(index, 32) net.WriteVector(position) net.WriteAngle(angles) net.WriteFloat(scale) net.WriteString(url) net.WriteUInt(brightness, 8) net.Broadcast() -- Save the plugin data. self:SavePanels() end -- Removes a panel that are within the radius of a position. function PLUGIN:RemovePanel(position, radius) -- Default the radius to 100. radius = radius or 100 local panelsDeleted = {} -- Loop through all of the panels. for k, v in pairs(self.list) do if (k == 0) then continue end -- Check if the distance from our specified position to the panel is less than the radius. if (v[1]:Distance(position) <= radius) then panelsDeleted[#panelsDeleted + 1] = k end end -- Save the plugin data if we actually changed anything. if (#panelsDeleted > 0) then -- Invert index table to delete from highest -> lowest panelsDeleted = table.Reverse(panelsDeleted) for _, v in ipairs(panelsDeleted) do -- Remove the panel from the list of panels. table.remove(self.list, v) -- Tell the players to stop showing the panel. net.Start("ixPanelRemove") net.WriteUInt(v, 32) net.Broadcast() end self:SavePanels() end -- Return the number of deleted panels. return #panelsDeleted end -- Called after entities have been loaded on the map. function PLUGIN:LoadData() self.list = self:GetData() or {} -- Formats table to sequential to support legacy panels. self.list = table.ClearKeys(self.list) end -- Called when the plugin needs to save information. function PLUGIN:SavePanels() self:SetData(self.list) end else -- Pre-define the zero index in client before the net receives PLUGIN.list[0] = PLUGIN.list[0] or 0 -- Holds the current cached material and filename. local cachedPreview = {} local function CacheMaterial(index) if (index < 1) then return end local info = PLUGIN.list[index] local exploded = string.Explode("/", info[6]) local filename = exploded[#exploded] local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/" if (file.Exists(path..filename, "DATA")) then local material = Material("../data/"..path..filename, "noclamp smooth") if (!material:IsError()) then info[7] = material -- Set width and height info[3] = material:GetInt("$realwidth") info[4] = material:GetInt("$realheight") end else file.CreateDir(path) http.Fetch(info[6], function(body) file.Write(path..filename, body) local material = Material("../data/"..path..filename, "noclamp smooth") if (!material:IsError()) then info[7] = material -- Set width and height info[3] = material:GetInt("$realwidth") info[4] = material:GetInt("$realheight") end end) end end local function UpdateCachedPreview(url) local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/" -- Gets the file name local exploded = string.Explode("/", url) local filename = exploded[#exploded] if (file.Exists(path..filename, "DATA")) then local preview = Material("../data/"..path..filename, "noclamp smooth") -- Update the cached preview if success if (!preview:IsError()) then cachedPreview = {url, preview} else cachedPreview = {} end else file.CreateDir(path) http.Fetch(url, function(body) file.Write(path..filename, body) local preview = Material("../data/"..path..filename, "noclamp smooth") -- Update the cached preview if success if (!preview:IsError()) then cachedPreview = {url, preview} else cachedPreview = {} end end) end end -- Receives new panel objects that need to be drawn. net.Receive("ixPanelAdd", function() local index = net.ReadUInt(32) local position = net.ReadVector() local angles = net.ReadAngle() local scale = net.ReadFloat() local url = net.ReadString() local brightness = net.ReadUInt(8) if (url != "") then PLUGIN.list[index] = {position, angles, nil, nil, scale, url, nil, brightness} CacheMaterial(index) PLUGIN.list[0] = #PLUGIN.list end end) net.Receive("ixPanelRemove", function() local index = net.ReadUInt(32) table.remove(PLUGIN.list, index) PLUGIN.list[0] = #PLUGIN.list end) -- Receives a full update on ALL panels. net.Receive("ixPanelList", function() local length = net.ReadUInt(32) local data = net.ReadData(length) local uncompressed = util.Decompress(data) if (!uncompressed) then ErrorNoHalt("[Helix] Unable to decompress panel data!\n") return end -- Set the list of panels to the ones provided by the server. PLUGIN.list = util.JSONToTable(uncompressed) -- Will be saved, but refresh just to make sure. PLUGIN.list[0] = #PLUGIN.list local CacheQueue = {} -- Loop through the list of panels. for k, _ in pairs(PLUGIN.list) do if (k == 0) then continue end CacheQueue[#CacheQueue + 1] = k end if (#CacheQueue == 0) then return end timer.Create("ixCache3DPanels", 1, #CacheQueue, function() if (#CacheQueue > 0) then CacheMaterial(CacheQueue[1]) table.remove(CacheQueue, 1) else timer.Remove("ixCache3DPanels") end end) end) -- Called after all translucent objects are drawn. function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox) if (bDrawingDepth or bDrawingSkybox) then return end -- Panel preview if (ix.chat.currentCommand == "paneladd") then self:PreviewPanel() end -- Store the position of the player to be more optimized. local ourPosition = LocalPlayer():GetPos() local panel = self.list for i = 1, panel[0] do local position = panel[i][1] local image = panel[i][7] -- Older panels do not have a brightness index local brightness = panel[i][8] or 255 if (panel[i][7] and ourPosition:DistToSqr(position) <= 4194304) then cam.Start3D2D(position, panel[i][2], panel[i][5] or 0.1) render.PushFilterMin(TEXFILTER.ANISOTROPIC) render.PushFilterMag(TEXFILTER.ANISOTROPIC) surface.SetDrawColor(brightness, brightness, brightness) surface.SetMaterial(image) surface.DrawTexturedRect(0, 0, panel[i][3] or image:Width(), panel[i][4] or image:Height()) render.PopFilterMag() render.PopFilterMin() cam.End3D2D() end end end function PLUGIN:ChatTextChanged(text) if (ix.chat.currentCommand == "paneladd") then -- Allow time for ix.chat.currentArguments to update timer.Simple(0, function() local arguments = ix.chat.currentArguments if (!arguments[1]) then return end UpdateCachedPreview(arguments[1]) end) end end function PLUGIN:PreviewPanel() local arguments = ix.chat.currentArguments -- if there's no URL, then no preview. if (!arguments[1]) then return end -- If the material is valid, preview the panel if (cachedPreview[2] and !cachedPreview[2]:IsError()) then local trace = LocalPlayer():GetEyeTrace() local angles = trace.HitNormal:Angle() angles:RotateAroundAxis(angles:Up(), 90) angles:RotateAroundAxis(angles:Forward(), 90) local position = (trace.HitPos + angles:Up() * 0.1) local ourPosition = LocalPlayer():GetPos() -- validate argument types local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5) local brightness = math.Clamp(math.Round((tonumber(arguments[3]) or 100) * 2.55), 1, 255) -- Attempt to collect the dimensions from the Material local width, height = cachedPreview[2]:GetInt("$realwidth"), cachedPreview[2]:GetInt("$realheight") if (ourPosition:DistToSqr(position) <= 4194304) then cam.Start3D2D(position, angles, scale or 0.1) render.PushFilterMin(TEXFILTER.ANISOTROPIC) render.PushFilterMag(TEXFILTER.ANISOTROPIC) surface.SetDrawColor(brightness, brightness, brightness) surface.SetMaterial(cachedPreview[2]) surface.DrawTexturedRect(0, 0, width or cachedPreview[2]:Width(), height or cachedPreview[2]:Height()) render.PopFilterMag() render.PopFilterMin() cam.End3D2D() end end end end ix.command.Add("PanelAdd", { description = "@cmdPanelAdd", privilege = "Manage Panels", adminOnly = true, arguments = { ix.type.string, bit.bor(ix.type.number, ix.type.optional), bit.bor(ix.type.number, ix.type.optional) }, OnRun = function(self, client, url, scale, brightness) -- Get the position and angles of the panel. local trace = client:GetEyeTrace() local position = trace.HitPos local angles = trace.HitNormal:Angle() angles:RotateAroundAxis(angles:Up(), 90) angles:RotateAroundAxis(angles:Forward(), 90) -- Add the panel. PLUGIN:AddPanel(position + angles:Up() * 0.1, angles, url, scale, brightness) return "@panelAdded" end }) ix.command.Add("PanelRemove", { description = "@cmdPanelRemove", privilege = "Manage Panels", adminOnly = true, arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, radius) -- Get the origin to remove panel. local trace = client:GetEyeTrace() local position = trace.HitPos -- Remove the panel(s) and get the amount removed. local amount = PLUGIN:RemovePanel(position, radius) return "@panelRemoved", amount end }) ================================================ FILE: plugins/3dtext.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "3D Text" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds text that can be placed on the map." -- List of available text panels PLUGIN.list = PLUGIN.list or {} if (SERVER) then util.AddNetworkString("ixTextList") util.AddNetworkString("ixTextAdd") util.AddNetworkString("ixTextRemove") ix.log.AddType("undo3dText", function(client) return string.format("%s has removed their last 3D text.", client:GetName()) end) -- Called when the player is sending client info. function PLUGIN:PlayerInitialSpawn(client) timer.Simple(1, function() if (IsValid(client)) then local json = util.TableToJSON(self.list) local compressed = util.Compress(json) local length = compressed:len() net.Start("ixTextList") net.WriteUInt(length, 32) net.WriteData(compressed, length) net.Send(client) end end) end -- Adds a text to the list, sends it to the players, and saves data. function PLUGIN:AddText(position, angles, text, scale) local index = #self.list + 1 scale = math.Clamp((scale or 1) * 0.1, 0.001, 5) self.list[index] = {position, angles, text, scale} net.Start("ixTextAdd") net.WriteUInt(index, 32) net.WriteVector(position) net.WriteAngle(angles) net.WriteString(text) net.WriteFloat(scale) net.Broadcast() self:SaveText() return index end -- Removes a text that are within the radius of a position. function PLUGIN:RemoveText(position, radius) radius = radius or 100 local textDeleted = {} for k, v in pairs(self.list) do if (k == 0) then continue end if (v[1]:Distance(position) <= radius) then textDeleted[#textDeleted + 1] = k end end if (#textDeleted > 0) then -- Invert index table to delete from highest -> lowest textDeleted = table.Reverse(textDeleted) for _, v in ipairs(textDeleted) do table.remove(self.list, v) net.Start("ixTextRemove") net.WriteUInt(v, 32) net.Broadcast() end self:SaveText() end return #textDeleted end function PLUGIN:RemoveTextByID(id) local info = self.list[id] if (!info) then return false end net.Start("ixTextRemove") net.WriteUInt(id, 32) net.Broadcast() table.remove(self.list, id) return true end -- Called after entities have been loaded on the map. function PLUGIN:LoadData() self.list = self:GetData() or {} -- Formats table to sequential to support legacy panels. self.list = table.ClearKeys(self.list) end -- Called when the plugin needs to save information. function PLUGIN:SaveText() self:SetData(self.list) end else -- Pre-define the zero index in client before the net receives PLUGIN.list[0] = PLUGIN.list[0] or 0 language.Add("Undone_ix3dText", "Removed 3D Text") function PLUGIN:GenerateMarkup(text) local object = ix.markup.Parse(""..text:gsub("\\n", "\n")) object.onDrawText = function(surfaceText, font, x, y, color, alignX, alignY, alpha) -- shadow surface.SetTextPos(x + 1, y + 1) surface.SetTextColor(0, 0, 0, alpha) surface.SetFont(font) surface.DrawText(surfaceText) surface.SetTextPos(x, y) surface.SetTextColor(color.r or 255, color.g or 255, color.b or 255, alpha) surface.SetFont(font) surface.DrawText(surfaceText) end return object end -- Receives new text objects that need to be drawn. net.Receive("ixTextAdd", function() local index = net.ReadUInt(32) local position = net.ReadVector() local angles = net.ReadAngle() local text = net.ReadString() local scale = net.ReadFloat() if (text != "") then PLUGIN.list[index] = { position, angles, PLUGIN:GenerateMarkup(text), scale } PLUGIN.list[0] = #PLUGIN.list end end) net.Receive("ixTextRemove", function() local index = net.ReadUInt(32) table.remove(PLUGIN.list, index) PLUGIN.list[0] = #PLUGIN.list end) -- Receives a full update on ALL texts. net.Receive("ixTextList", function() local length = net.ReadUInt(32) local data = net.ReadData(length) local uncompressed = util.Decompress(data) if (!uncompressed) then ErrorNoHalt("[Helix] Unable to decompress text data!\n") return end PLUGIN.list = util.JSONToTable(uncompressed) -- Will be saved, but refresh just to make sure. PLUGIN.list[0] = #PLUGIN.list for k, v in pairs(PLUGIN.list) do if (k == 0) then continue end local object = ix.markup.Parse(""..v[3]:gsub("\\n", "\n")) object.onDrawText = function(text, font, x, y, color, alignX, alignY, alpha) draw.TextShadow({ pos = {x, y}, color = ColorAlpha(color, alpha), text = text, xalign = 0, yalign = alignY, font = font }, 1, alpha) end v[3] = object end end) function PLUGIN:StartChat() self.preview = nil end function PLUGIN:FinishChat() self.preview = nil end function PLUGIN:HUDPaint() if (ix.chat.currentCommand != "textremove") then return end local radius = tonumber(ix.chat.currentArguments[1]) or 100 surface.SetDrawColor(200, 30, 30) surface.SetTextColor(200, 30, 30) surface.SetFont("ixMenuButtonFont") local i = 0 for k, v in pairs(self.list) do if (k == 0) then continue end if (v[1]:Distance(LocalPlayer():GetEyeTraceNoCursor().HitPos) <= radius) then local screen = v[1]:ToScreen() surface.DrawLine( ScrW() * 0.5, ScrH() * 0.5, math.Clamp(screen.x, 0, ScrW()), math.Clamp(screen.y, 0, ScrH()) ) i = i + 1 end end if (i > 0) then local textWidth, textHeight = surface.GetTextSize(i) surface.SetTextPos(ScrW() * 0.5 - textWidth * 0.5, ScrH() * 0.5 + textHeight + 8) surface.DrawText(i) end end function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox) if (bDrawingDepth or bDrawingSkybox) then return end -- preview for textadd command if (ix.chat.currentCommand == "textadd") then local arguments = ix.chat.currentArguments local text = tostring(arguments[1] or "") local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5) local trace = LocalPlayer():GetEyeTraceNoCursor() local position = trace.HitPos local angles = trace.HitNormal:Angle() local markup angles:RotateAroundAxis(angles:Up(), 90) angles:RotateAroundAxis(angles:Forward(), 90) -- markup will error with invalid fonts pcall(function() markup = PLUGIN:GenerateMarkup(text) end) if (markup) then cam.Start3D2D(position, angles, scale) markup:draw(0, 0, 1, 1, 255) cam.End3D2D() end end local position = LocalPlayer():GetPos() local texts = self.list for i = 1, texts[0] do local distance = texts[i][1]:DistToSqr(position) if (distance > 1048576) then continue end cam.Start3D2D(texts[i][1], texts[i][2], texts[i][4] or 0.1) local alpha = (1 - ((distance - 65536) / 768432)) * 255 texts[i][3]:draw(0, 0, 1, 1, alpha) cam.End3D2D() end end end ix.command.Add("TextAdd", { description = "@cmdTextAdd", adminOnly = true, arguments = { ix.type.string, bit.bor(ix.type.number, ix.type.optional) }, OnRun = function(self, client, text, scale) local trace = client:GetEyeTrace() local position = trace.HitPos local angles = trace.HitNormal:Angle() angles:RotateAroundAxis(angles:Up(), 90) angles:RotateAroundAxis(angles:Forward(), 90) local index = PLUGIN:AddText(position + angles:Up() * 0.1, angles, text, scale) undo.Create("ix3dText") undo.SetPlayer(client) undo.AddFunction(function() if (PLUGIN:RemoveTextByID(index)) then ix.log.Add(client, "undo3dText") end end) undo.Finish() return "@textAdded" end }) ix.command.Add("TextRemove", { description = "@cmdTextRemove", adminOnly = true, arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, radius) local trace = client:GetEyeTrace() local position = trace.HitPos + trace.HitNormal * 2 local amount = PLUGIN:RemoveText(position, radius) return "@textRemoved", amount end }) ================================================ FILE: plugins/act/cl_hooks.lua ================================================ local animationTime = 2 local PLUGIN = PLUGIN PLUGIN.cameraFraction = 0 local function GetHeadBone(client) local head for i = 1, client:GetBoneCount() do local name = client:GetBoneName(i) if (string.find(name:lower(), "head")) then head = i break end end return head end function PLUGIN:PlayerBindPress(client, bind, bPressed) if (!client:GetNetVar("actEnterAngle")) then return end if (bind:find("+jump") and bPressed) then ix.command.Send("ExitAct") return true end end function PLUGIN:ShouldDrawLocalPlayer(client) if (client:GetNetVar("actEnterAngle") and self.cameraFraction > 0.25) then return true elseif (self.cameraFraction > 0.25) then return true end end local forwardOffset = 16 local backwardOffset = -32 local heightOffset = Vector(0, 0, 20) local idleHeightOffset = Vector(0, 0, 6) local traceMin = Vector(-4, -4, -4) local traceMax = Vector(4, 4, 4) function PLUGIN:CalcView(client, origin) local enterAngle = client:GetNetVar("actEnterAngle") local fraction = self.cameraFraction local offset = self.bIdle and forwardOffset or backwardOffset local height = self.bIdle and idleHeightOffset or heightOffset if (!enterAngle) then if (fraction > 0) then local view = { origin = LerpVector(fraction, origin, origin + self.forward * offset + height) } if (self.cameraTween) then self.cameraTween:update(FrameTime()) end return view end return end local view = {} local forward = enterAngle:Forward() local head = GetHeadBone(client) local bFirstPerson = true if (ix.option.Get("thirdpersonEnabled", false)) then local originPosition = head and client:GetBonePosition(head) or client:GetPos() -- check if the camera will hit something local data = util.TraceHull({ start = originPosition, endpos = originPosition - client:EyeAngles():Forward() * 48, mins = traceMin * 0.75, maxs = traceMax * 0.75, filter = client }) bFirstPerson = data.Hit if (!bFirstPerson) then view.origin = data.HitPos end end if (bFirstPerson) then if (head) then local position = client:GetBonePosition(head) + forward * offset + height local data = { start = (client:GetBonePosition(head) or Vector(0, 0, 64)) + forward * 8, endpos = position + forward * offset, mins = traceMin, maxs = traceMax, filter = client } data = util.TraceHull(data) if (data.Hit) then view.origin = data.HitPos else view.origin = position end else view.origin = origin + forward * forwardOffset + height end end view.origin = LerpVector(fraction, origin, view.origin) if (self.cameraTween) then self.cameraTween:update(FrameTime()) end return view end net.Receive("ixActEnter", function() PLUGIN.bIdle = net.ReadBool() PLUGIN.forward = LocalPlayer():GetNetVar("actEnterAngle"):Forward() PLUGIN.cameraTween = ix.tween.new(animationTime, PLUGIN, { cameraFraction = 1 }, "outQuint") end) net.Receive("ixActLeave", function() PLUGIN.cameraTween = ix.tween.new(animationTime * 0.5, PLUGIN, { cameraFraction = 0 }, "outQuint") end) ================================================ FILE: plugins/act/sh_definitions.lua ================================================ local function FacingWall(client) local data = {} data.start = client:EyePos() data.endpos = data.start + client:GetForward() * 20 data.filter = client if (!util.TraceLine(data).Hit) then return "@faceWall" end end local function FacingWallBack(client) local data = {} data.start = client:LocalToWorld(client:OBBCenter()) data.endpos = data.start - client:GetForward() * 20 data.filter = client if (!util.TraceLine(data).Hit) then return "@faceWallBack" end end function PLUGIN:SetupActs() -- sit ix.act.Register("Sit", {"citizen_male", "citizen_female"}, { start = {"idle_to_sit_ground", "idle_to_sit_chair"}, sequence = {"sit_ground", "sit_chair"}, finish = { {"sit_ground_to_idle", duration = 2.1}, "" }, untimed = true, idle = true }) ix.act.Register("SitWall", {"citizen_male", "citizen_female"}, { sequence = { {"plazaidle4", check = FacingWallBack}, {"injured1", check = FacingWallBack, offset = function(client) return client:GetForward() * 14 end} }, untimed = true, idle = true }) ix.act.Register("Sit", "vortigaunt", { sequence = "chess_wait", untimed = true, idle = true }) -- stand ix.act.Register("Stand", "citizen_male", { sequence = {"lineidle01", "lineidle02", "lineidle03", "lineidle04"}, untimed = true, idle = true }) ix.act.Register("Stand", "citizen_female", { sequence = {"lineidle01", "lineidle02", "lineidle03"}, untimed = true, idle = true }) ix.act.Register("Stand", "metrocop", { sequence = "plazathreat2" }) -- cheer ix.act.Register("Cheer", "citizen_male", { sequence = {{"cheer1", duration = 1.6}, "cheer2", "wave_smg1"} }) ix.act.Register("Cheer", "citizen_female", { sequence = {"cheer1", "wave_smg1"} }) -- lean ix.act.Register("Lean", {"citizen_male", "citizen_female"}, { start = {"idle_to_lean_back", "", ""}, sequence = { {"lean_back", check = FacingWallBack}, {"plazaidle1", check = FacingWallBack}, {"plazaidle2", check = FacingWallBack} }, untimed = true, idle = true }) ix.act.Register("Lean", {"metrocop"}, { sequence = {{"idle_baton", check = FacingWallBack}, "busyidle2"}, untimed = true, idle = true }) -- injured ix.act.Register("Injured", "citizen_male", { sequence = {"d1_town05_wounded_idle_1", "d1_town05_wounded_idle_2", "d1_town05_winston_down"}, untimed = true, idle = true }) ix.act.Register("Injured", "citizen_female", { sequence = "d1_town05_wounded_idle_1", untimed = true, idle = true }) -- arrest ix.act.Register("ArrestWall", "citizen_male", { sequence = { {"apcarrestidle", check = FacingWall, offset = function(client) return -client:GetForward() * 23 end}, "spreadwallidle" }, untimed = true }) ix.act.Register("Arrest", "citizen_male", { sequence = "arrestidle", untimed = true }) -- threat ix.act.Register("Threat", "metrocop", { sequence = "plazathreat1", }) -- deny ix.act.Register("Deny", "metrocop", { sequence = "harassfront2", }) -- motion ix.act.Register("Motion", "metrocop", { sequence = {"motionleft", "motionright", "luggagewarn"} }) -- wave ix.act.Register("Wave", {"citizen_male", "citizen_female"}, { sequence = {{"wave", duration = 2.75}, {"wave_close", duration = 1.75}} }) -- pant ix.act.Register("Pant", {"citizen_male", "citizen_female"}, { start = {"d2_coast03_postbattle_idle02_entry", "d2_coast03_postbattle_idle01_entry"}, sequence = {"d2_coast03_postbattle_idle02", {"d2_coast03_postbattle_idle01", check = FacingWall}}, untimed = true }) -- window ix.act.Register("Window", "citizen_male", { sequence = "d1_t03_tenements_look_out_window_idle", untimed = true }) ix.act.Register("Window", "citizen_female", { sequence = "d1_t03_lookoutwindow", untimed = true }) end ================================================ FILE: plugins/act/sh_plugin.lua ================================================ --[[-- Provides players the ability to perform animations. ]] -- @module ix.act local PLUGIN = PLUGIN PLUGIN.name = "Player Acts" PLUGIN.description = "Adds animations that can be performed by certain models." PLUGIN.author = "`impulse" ix.act = ix.act or {} ix.act.stored = ix.act.stored or {} CAMI.RegisterPrivilege({ Name = "Helix - Player Acts", MinAccess = "user" }) --- Registers a sequence as a performable animation. -- @realm shared -- @string name Name of the animation (in CamelCase) -- @string modelClass Model class to add this animation to -- @tab data An `ActInfoStructure` table describing the animation function ix.act.Register(name, modelClass, data) ix.act.stored[name] = ix.act.stored[name] or {} -- might be adding onto an existing act if (!data.sequence) then return ErrorNoHalt(string.format( "Act '%s' for '%s' tried to register without a provided sequence\n", name, modelClass )) end if (!istable(data.sequence)) then data.sequence = {data.sequence} end if (data.start and istable(data.start) and #data.start != #data.sequence) then return ErrorNoHalt(string.format( "Act '%s' tried to register without matching number of enter sequences\n", name )) end if (data.finish and istable(data.finish) and #data.finish != #data.sequence) then return ErrorNoHalt(string.format( "Act '%s' tried to register without matching number of exit sequences\n", name )) end if (istable(modelClass)) then for _, v in ipairs(modelClass) do ix.act.stored[name][v] = data end else ix.act.stored[name][modelClass] = data end end --- Removes a sequence from being performable if it has been previously registered. -- @realm shared -- @string name Name of the animation function ix.act.Remove(name) ix.act.stored[name] = nil ix.command.list["Act" .. name] = nil end ix.util.Include("sh_definitions.lua") ix.util.Include("sv_hooks.lua") ix.util.Include("cl_hooks.lua") function PLUGIN:InitializedPlugins() hook.Run("SetupActs") hook.Run("PostSetupActs") end function PLUGIN:ExitAct(client) client.ixUntimedSequence = nil client:SetNetVar("actEnterAngle") net.Start("ixActLeave") net.Send(client) end function PLUGIN:PostSetupActs() -- create chat commands for all stored acts for act, classes in pairs(ix.act.stored) do local variants = 1 local COMMAND = { privilege = "Player Acts" } -- check if this act has any variants (i.e /ActSit 2) for _, v in pairs(classes) do if (#v.sequence > 1) then variants = math.max(variants, #v.sequence) end end -- setup command arguments if there are variants for this act if (variants > 1) then COMMAND.arguments = bit.bor(ix.type.number, ix.type.optional) COMMAND.argumentNames = {"variant (1-" .. variants .. ")"} end COMMAND.GetDescription = function(command) return L("cmdAct", act) end local privilege = "Helix - " .. COMMAND.privilege -- we'll perform a model class check in OnCheckAccess to prevent the command from showing up on the client at all COMMAND.OnCheckAccess = function(command, client) local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil) if (!bHasAccess) then return false end local modelClass = ix.anim.GetModelClass(client:GetModel()) if (!classes[modelClass]) then return false, "modelNoSeq" end return true end COMMAND.OnRun = function(command, client, variant) variant = math.Clamp(tonumber(variant) or 1, 1, variants) if (client:GetNetVar("actEnterAngle")) then return "@notNow" end local modelClass = ix.anim.GetModelClass(client:GetModel()) local bCanEnter, error = PLUGIN:CanPlayerEnterAct(client, modelClass, variant, classes) if (!bCanEnter) then return error end local data = classes[modelClass] local mainSequence = data.sequence[variant] local mainDuration -- check if the main sequence has any extra info if (istable(mainSequence)) then -- any validity checks to perform (i.e facing a wall) if (mainSequence.check) then local result = mainSequence.check(client) if (result) then return result end end -- position offset if (mainSequence.offset) then client.ixOldPosition = client:GetPos() client:SetPos(client:GetPos() + mainSequence.offset(client)) end mainDuration = mainSequence.duration mainSequence = mainSequence[1] end local startSequence = data.start and data.start[variant] or "" local startDuration if (istable(startSequence)) then startDuration = startSequence.duration startSequence = startSequence[1] end client:SetNetVar("actEnterAngle", client:GetAngles()) client:ForceSequence(startSequence, function() -- we've finished the start sequence client.ixUntimedSequence = data.untimed -- client can exit after the start sequence finishes playing local duration = client:ForceSequence(mainSequence, function() -- we've stopped playing the main sequence (either duration expired or user cancelled the act) if (data.finish) then local finishSequence = data.finish[variant] local finishDuration if (istable(finishSequence)) then finishDuration = finishSequence.duration finishSequence = finishSequence[1] end client:ForceSequence(finishSequence, function() -- client has finished the end sequence and is no longer playing any animations self:ExitAct(client) end, finishDuration) else -- there's no end sequence so we can exit right away self:ExitAct(client) end end, data.untimed and 0 or (mainDuration or nil)) if (!duration) then -- the model doesn't support this variant self:ExitAct(client) client:NotifyLocalized("modelNoSeq") return end end, startDuration, nil) net.Start("ixActEnter") net.WriteBool(data.idle or false) net.Send(client) client.ixNextAct = CurTime() + 4 end ix.command.Add("Act" .. act, COMMAND) end -- setup exit act command local COMMAND = { privilege = "Player Acts", OnRun = function(command, client) if (client.ixUntimedSequence) then client:LeaveSequence() end end } if (CLIENT) then -- hide this command from the command list COMMAND.OnCheckAccess = function(client) return false end end ix.command.Add("ExitAct", COMMAND) end function PLUGIN:UpdateAnimation(client, moveData) local angle = client:GetNetVar("actEnterAngle") if (angle) then client:SetRenderAngles(angle) end end do local keyBlacklist = IN_ATTACK + IN_ATTACK2 function PLUGIN:StartCommand(client, command) if (client:GetNetVar("actEnterAngle")) then command:RemoveKey(keyBlacklist) end end end ================================================ FILE: plugins/act/sv_hooks.lua ================================================ local PLUGIN = PLUGIN util.AddNetworkString("ixActEnter") util.AddNetworkString("ixActLeave") function PLUGIN:CanPlayerEnterAct(client, modelClass, variant, act) if (!client:Alive() or client:GetLocalVar("ragdoll") or client:WaterLevel() > 0 or !client:IsOnGround()) then return false, L("notNow", client) end -- check if player's model class has an entry in this act table modelClass = modelClass or ix.anim.GetModelClass(client:GetModel()) local data = act[modelClass] if (!data) then return false, L("modelNoSeq", client) end -- some models don't support certain variants local sequence = data.sequence[variant] if (!sequence) then return false, L("modelNoSeq", client) end return true end function PLUGIN:PlayerDeath(client) if (client.ixUntimedSequence) then client:SetNetVar("actEnterAngle") client:LeaveSequence() client.ixUntimedSequence = nil end end function PLUGIN:PlayerSpawn(client) if (client.ixUntimedSequence) then client:SetNetVar("actEnterAngle") client:LeaveSequence() client.ixUntimedSequence = nil end end function PLUGIN:OnCharacterFallover(client) if (client.ixUntimedSequence) then client:SetNetVar("actEnterAngle") client:LeaveSequence() client.ixUntimedSequence = nil end end ================================================ FILE: plugins/ammosave.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Ammo Saver" PLUGIN.author = "Black Tea" PLUGIN.description = "Saves the ammo of a character." PLUGIN.ammoList = {} ix.ammo = ix.ammo or {} function ix.ammo.Register(name) name = name:lower() if (!table.HasValue(PLUGIN.ammoList, name)) then PLUGIN.ammoList[#PLUGIN.ammoList + 1] = name end end -- Register Default HL2 Ammunition. ix.ammo.Register("ar2") ix.ammo.Register("pistol") ix.ammo.Register("357") ix.ammo.Register("smg1") ix.ammo.Register("xbowbolt") ix.ammo.Register("buckshot") ix.ammo.Register("rpg_round") ix.ammo.Register("smg1_grenade") ix.ammo.Register("grenade") ix.ammo.Register("ar2altfire") ix.ammo.Register("slam") -- Register Cut HL2 Ammunition. ix.ammo.Register("alyxgun") ix.ammo.Register("sniperround") ix.ammo.Register("sniperpenetratedround") ix.ammo.Register("thumper") ix.ammo.Register("gravity") ix.ammo.Register("battery") ix.ammo.Register("gaussenergy") ix.ammo.Register("combinecannon") ix.ammo.Register("airboatgun") ix.ammo.Register("striderminigun") ix.ammo.Register("helicoptergun") -- Called right before the character has its information save. function PLUGIN:CharacterPreSave(character) -- Get the player from the character. local client = character:GetPlayer() -- Check to see if we can get the player's ammo. if (IsValid(client)) then local ammoTable = {} for _, v in ipairs(self.ammoList) do local ammo = client:GetAmmoCount(v) if (ammo > 0) then ammoTable[v] = ammo end end character:SetData("ammo", ammoTable) end end -- Called after the player's loadout has been set. function PLUGIN:PlayerLoadedCharacter(client) timer.Simple(0.25, function() if (!IsValid(client)) then return end -- Get the saved ammo table from the character data. local character = client:GetCharacter() if (!character) then return end local ammoTable = character:GetData("ammo") -- Check if the ammotable is exists. if (ammoTable) then for k, v in pairs(ammoTable) do client:SetAmmo(v, tostring(k)) end end end) end ================================================ FILE: plugins/area/cl_hooks.lua ================================================ local PLUGIN = PLUGIN local function DrawTextBackground(x, y, text, font, backgroundColor, padding) font = font or "ixSubTitleFont" padding = padding or 8 backgroundColor = backgroundColor or Color(88, 88, 88, 255) surface.SetFont(font) local textWidth, textHeight = surface.GetTextSize(text) local width, height = textWidth + padding * 2, textHeight + padding * 2 ix.util.DrawBlurAt(x, y, width, height) surface.SetDrawColor(0, 0, 0, 40) surface.DrawRect(x, y, width, height) derma.SkinFunc("DrawImportantBackground", x, y, width, height, backgroundColor) surface.SetTextColor(color_white) surface.SetTextPos(x + padding, y + padding) surface.DrawText(text) return height end function PLUGIN:InitPostEntity() hook.Run("SetupAreaProperties") end function PLUGIN:ChatboxCreated() if (IsValid(self.panel)) then self.panel:Remove() end self.panel = vgui.Create("ixArea") end function PLUGIN:ChatboxPositionChanged(x, y, width, height) if (!IsValid(self.panel)) then return end self.panel:SetSize(width, y) self.panel:SetPos(32, 0) end function PLUGIN:ShouldDrawCrosshair() if (ix.area.bEditing) then return true end end function PLUGIN:PlayerBindPress(client, bind, bPressed) if (!ix.area.bEditing) then return end if ((bind:find("invnext") or bind:find("invprev")) and bPressed) then return true elseif (bind:find("attack2") and bPressed) then self:EditRightClick() return true elseif (bind:find("attack") and bPressed) then self:EditClick() return true elseif (bind:find("reload") and bPressed) then self:EditReload() return true end end function PLUGIN:HUDPaint() if (!ix.area.bEditing) then return end local id = LocalPlayer():GetArea() local area = ix.area.stored[id] local height = ScrH() local y = 64 y = y + DrawTextBackground(64, y, L("areaEditMode"), nil, ix.config.Get("color")) if (!self.editStart) then y = y + DrawTextBackground(64, y, L("areaEditTip"), "ixSmallTitleFont") DrawTextBackground(64, y, L("areaRemoveTip"), "ixSmallTitleFont") else DrawTextBackground(64, y, L("areaFinishTip"), "ixSmallTitleFont") end if (area) then DrawTextBackground(64, height - 64 - ScreenScale(12), id, "ixSmallTitleFont", area.properties.color) end end function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox) if (bSkybox or !ix.area.bEditing) then return end -- draw all areas for k, v in pairs(ix.area.stored) do local center, min, max = self:GetLocalAreaPosition(v.startPosition, v.endPosition) local color = ColorAlpha(v.properties.color or ix.config.Get("color"), 255) render.DrawWireframeBox(center, angle_zero, min, max, color) cam.Start2D() local centerScreen = center:ToScreen() local _, textHeight = draw.SimpleText( k, "BudgetLabel", centerScreen.x, centerScreen.y, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) if (v.type != "area") then draw.SimpleText( "(" .. L(v.type) .. ")", "BudgetLabel", centerScreen.x, centerScreen.y + textHeight, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) end cam.End2D() end -- draw currently edited area if (self.editStart) then local center, min, max = self:GetLocalAreaPosition(self.editStart, self:GetPlayerAreaTrace().HitPos) local color = Color(255, 255, 255, 25 + (1 + math.sin(SysTime() * 6)) * 115) render.DrawWireframeBox(center, angle_zero, min, max, color) cam.Start2D() local centerScreen = center:ToScreen() draw.SimpleText(L("areaNew"), "BudgetLabel", centerScreen.x, centerScreen.y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) cam.End2D() end end function PLUGIN:EditRightClick() if (self.editStart) then self.editStart = nil else self:StopEditing() end end function PLUGIN:EditClick() if (!self.editStart) then self.editStart = LocalPlayer():GetEyeTraceNoCursor().HitPos elseif (self.editStart and !self.editProperties) then self.editProperties = true local panel = vgui.Create("ixAreaEdit") panel:MakePopup() end end function PLUGIN:EditReload() if (self.editStart) then return end local id = LocalPlayer():GetArea() local area = ix.area.stored[id] if (!area) then return end Derma_Query(L("areaDeleteConfirm", id), L("areaDelete"), L("no"), nil, L("yes"), function() net.Start("ixAreaRemove") net.WriteString(id) net.SendToServer() end ) end function PLUGIN:ShouldDisplayArea(id) if (ix.area.bEditing) then return false end end function PLUGIN:OnAreaChanged(oldID, newID) local client = LocalPlayer() client.ixArea = newID local area = ix.area.stored[newID] if (!area) then client.ixInArea = false return end client.ixInArea = true if (hook.Run("ShouldDisplayArea", newID) == false or !area.properties.display) then return end local format = newID .. (ix.option.Get("24hourTime", false) and ", %H:%M." or ", %I:%M %p.") format = ix.date.GetFormatted(format) self.panel:AddEntry(format, area.properties.color) end net.Receive("ixAreaEditStart", function() PLUGIN:StartEditing() end) net.Receive("ixAreaEditEnd", function() PLUGIN:StopEditing() end) net.Receive("ixAreaAdd", function() local name = net.ReadString() local type = net.ReadString() local startPosition, endPosition = net.ReadVector(), net.ReadVector() local properties = net.ReadTable() if (name != "") then ix.area.stored[name] = { type = type, startPosition = startPosition, endPosition = endPosition, properties = properties } end end) net.Receive("ixAreaRemove", function() local name = net.ReadString() if (ix.area.stored[name]) then ix.area.stored[name] = nil end end) net.Receive("ixAreaSync", function() local length = net.ReadUInt(32) local data = net.ReadData(length) local uncompressed = util.Decompress(data) if (!uncompressed) then ErrorNoHalt("[Helix] Unable to decompress area data!\n") return end -- Set the list of texts to the ones provided by the server. ix.area.stored = util.JSONToTable(uncompressed) end) net.Receive("ixAreaChanged", function() local oldID, newID = net.ReadString(), net.ReadString() hook.Run("OnAreaChanged", oldID, newID) end) ================================================ FILE: plugins/area/cl_plugin.lua ================================================ local PLUGIN = PLUGIN function PLUGIN:GetPlayerAreaTrace() local client = LocalPlayer() return util.TraceLine({ start = client:GetShootPos(), endpos = client:GetShootPos() + client:GetForward() * 96, filter = client }) end function PLUGIN:StartEditing() ix.area.bEditing = true self.editStart = nil self.editProperties = nil end function PLUGIN:StopEditing() ix.area.bEditing = false if (IsValid(ix.gui.areaEdit)) then ix.gui.areaEdit:Remove() end end ================================================ FILE: plugins/area/derma/cl_area.lua ================================================ -- area entry DEFINE_BASECLASS("Panel") local PANEL = {} AccessorFunc(PANEL, "text", "Text", FORCE_STRING) AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") AccessorFunc(PANEL, "tickSound", "TickSound", FORCE_STRING) AccessorFunc(PANEL, "tickSoundRange", "TickSoundRange") AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha", FORCE_NUMBER) AccessorFunc(PANEL, "expireTime", "ExpireTime", FORCE_NUMBER) AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) function PANEL:Init() self:DockPadding(4, 4, 4, 4) self:SetSize(self:GetParent():GetWide(), 0) self.label = self:Add("DLabel") self.label:Dock(FILL) self.label:SetFont("ixMediumLightFont") self.label:SetTextColor(color_white) self.label:SetExpensiveShadow(1, color_black) self.label:SetText("Area") self.text = "" self.tickSound = "ui/buttonrollover.wav" self.tickSoundRange = {190, 200} self.backgroundAlpha = 255 self.expireTime = 8 self.animationTime = 2 self.character = 1 self.createTime = RealTime() self.currentAlpha = 255 self.currentHeight = 0 self.nextThink = RealTime() end function PANEL:Show() self:CreateAnimation(0.5, { index = -1, target = {currentHeight = self.label:GetTall() + 8}, easing = "outQuint", Think = function(animation, panel) panel:SetTall(panel.currentHeight) end }) end function PANEL:SetFont(font) self.label:SetFont(font) end function PANEL:SetText(text) if (text:sub(1, 1) == "@") then text = L(text:sub(2)) end self.label:SetText(text) self.text = text self.character = 1 end function PANEL:Think() local time = RealTime() if (time >= self.nextThink) then if (self.character < self.text:utf8len()) then self.character = self.character + 1 self.label:SetText(string.utf8sub(self.text, 1, self.character)) LocalPlayer():EmitSound(self.tickSound, 100, math.random(self.tickSoundRange[1], self.tickSoundRange[2])) end if (time >= self.createTime + self.expireTime and !self.bRemoving) then self:Remove() end self.nextThink = time + 0.05 end end function PANEL:SizeToContents() self:SetWide(self:GetParent():GetWide()) self.label:SetWide(self:GetWide()) self.label:SizeToContentsY() end function PANEL:Paint(width, height) self.backgroundAlpha = math.max(self.backgroundAlpha - 200 * FrameTime(), 0) derma.SkinFunc("PaintAreaEntry", self, width, height) end function PANEL:Remove() if (self.bRemoving) then return end self:CreateAnimation(self.animationTime, { target = {currentAlpha = 0}, Think = function(animation, panel) panel:SetAlpha(panel.currentAlpha) end, OnComplete = function(animation, panel) panel:CreateAnimation(0.5, { index = -1, target = {currentHeight = 0}, easing = "outQuint", Think = function(_, sizePanel) sizePanel:SetTall(sizePanel.currentHeight) end, OnComplete = function(_, sizePanel) sizePanel:OnRemove() BaseClass.Remove(sizePanel) end }) end }) self.bRemoving = true end function PANEL:OnRemove() end vgui.Register("ixAreaEntry", PANEL, "Panel") -- main panel PANEL = {} function PANEL:Init() local chatWidth, _ = chat.GetChatBoxSize() local _, chatY = chat.GetChatBoxPos() self:SetSize(chatWidth, chatY) self:SetPos(32, 0) self:ParentToHUD() self.entries = {} ix.gui.area = self end function PANEL:AddEntry(entry, color) color = color or ix.config.Get("color") local id = #self.entries + 1 local panel = entry if (isstring(entry)) then panel = self:Add("ixAreaEntry") panel:SetText(entry) end panel:SetBackgroundColor(color) panel:SizeToContents() panel:Dock(BOTTOM) panel:Show() panel.OnRemove = function() for k, v in pairs(self.entries) do if (v == panel) then table.remove(self.entries, k) break end end end self.entries[id] = panel return id end function PANEL:GetEntries() return self.entries end vgui.Register("ixArea", PANEL, "Panel") ================================================ FILE: plugins/area/derma/cl_areaedit.lua ================================================ local PLUGIN = PLUGIN local PANEL = {} function PANEL:Init() if (IsValid(ix.gui.areaEdit)) then ix.gui.areaEdit:Remove() end ix.gui.areaEdit = self self.list = {} self.properties = {} self:SetDeleteOnClose(true) self:SetSizable(true) self:SetTitle(L("areaNew")) -- scroll panel self.canvas = self:Add("DScrollPanel") self.canvas:Dock(FILL) -- name entry self.nameEntry = vgui.Create("ixTextEntry") self.nameEntry:SetFont("ixMediumLightFont") self.nameEntry:SetText(L("areaNew")) local listRow = self.canvas:Add("ixListRow") listRow:SetList(self.list) listRow:SetLabelText(L("name")) listRow:SetRightPanel(self.nameEntry) listRow:Dock(TOP) listRow:SizeToContents() -- type entry self.typeEntry = self.canvas:Add("DComboBox") self.typeEntry:Dock(RIGHT) self.typeEntry:SetFont("ixMediumLightFont") self.typeEntry:SetTextColor(color_black) self.typeEntry.OnSelect = function(panel) panel:SizeToContents() panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice) end for id, name in pairs(ix.area.types) do self.typeEntry:AddChoice(L(name), id, id == "area") end listRow = self.canvas:Add("ixListRow") listRow:SetList(self.list) listRow:SetLabelText(L("type")) listRow:SetRightPanel(self.typeEntry) listRow:Dock(TOP) listRow:SizeToContents() -- properties for k, v in pairs(ix.area.properties) do local panel if (v.type == ix.type.string or v.type == ix.type.number) then panel = vgui.Create("ixTextEntry") panel:SetFont("ixMenuButtonFont") panel:SetText(tostring(v.default)) if (v.type == ix.type.number) then panel.realGetValue = panel.GetValue panel.GetValue = function() return tonumber(panel:realGetValue()) or v.default end end elseif (v.type == ix.type.bool) then panel = vgui.Create("ixCheckBox") panel:SetChecked(v.default, true) panel:SetFont("ixMediumLightFont") elseif (v.type == ix.type.color) then panel = vgui.Create("DButton") panel.value = v.default panel:SetText("") panel:SetSize(64, 64) panel.picker = vgui.Create("DColorCombo") panel.picker:SetColor(panel.value) panel.picker:SetVisible(false) panel.picker.OnValueChanged = function(_, newColor) panel.value = newColor end panel.Paint = function(_, width, height) surface.SetDrawColor(0, 0, 0, 255) surface.DrawOutlinedRect(0, 0, width, height) surface.SetDrawColor(panel.value) surface.DrawRect(4, 4, width - 8, height - 8) end panel.DoClick = function() if (!panel.picker:IsVisible()) then local x, y = panel:LocalToScreen(0, 0) panel.picker:SetPos(x, y + 32) panel.picker:SetColor(panel.value) panel.picker:SetVisible(true) panel.picker:MakePopup() else panel.picker:SetVisible(false) end end panel.OnRemove = function() panel.picker:Remove() end panel.GetValue = function() return panel.picker:GetColor() end end if (IsValid(panel)) then local row = self.canvas:Add("ixListRow") row:SetList(self.list) row:SetLabelText(L(k)) row:SetRightPanel(panel) row:Dock(TOP) row:SizeToContents() end self.properties[k] = function() return panel:GetValue() end end -- save button self.saveButton = self:Add("DButton") self.saveButton:SetText(L("save")) self.saveButton:SizeToContents() self.saveButton:Dock(BOTTOM) self.saveButton.DoClick = function() self:Submit() end self:SizeToContents() self:SetPos(64, 0) self:CenterVertical() end function PANEL:SizeToContents() local width = 600 local height = 37 for _, v in ipairs(self.canvas:GetCanvas():GetChildren()) do width = math.max(width, v:GetLabelWidth()) height = height + v:GetTall() end self:SetWide(width + 200) self:SetTall(height + self.saveButton:GetTall() + 50) end function PANEL:Submit() local name = self.nameEntry:GetValue() if (ix.area.stored[name]) then ix.util.NotifyLocalized("areaAlreadyExists") return end local properties = {} for k, v in pairs(self.properties) do properties[k] = v() end local _, type = self.typeEntry:GetSelected() net.Start("ixAreaAdd") net.WriteString(name) net.WriteString(type) net.WriteVector(PLUGIN.editStart) net.WriteVector(PLUGIN:GetPlayerAreaTrace().HitPos) net.WriteTable(properties) net.SendToServer() PLUGIN.editStart = nil self:Remove() end function PANEL:OnRemove() PLUGIN.editProperties = nil end vgui.Register("ixAreaEdit", PANEL, "DFrame") if (IsValid(ix.gui.areaEdit)) then ix.gui.areaEdit:Remove() end ================================================ FILE: plugins/area/languages/sh_english.lua ================================================ LANGUAGE = { area = "Area", areas = "Areas", areaEditMode = "Area Edit Mode", areaNew = "New Area", areaAlreadyExists = "An area with this name already exists!", areaDoesntExist = "An area with that name doesn't exist!", areaInvalidType = "You have specified an invalid area type!", areaEditTip = "Click to start creating an area. Right click to exit.", areaFinishTip = "Click again to finish drawing the area. Right click to go back.", areaRemoveTip = "Press reload to remove the area you're currently in.", areaDeleteConfirm = "Are you sure you want to delete the area \"%s\"?", areaDelete = "Delete Area", cmdAreaEdit = "Enters area edit mode." } ================================================ FILE: plugins/area/languages/sh_russian.lua ================================================ LANGUAGE = { area = "Зона", areas = "Зоны", areaEditMode = "Редактирование зоны", areaNew = "Новая зона", areaAlreadyExists = "Зона с таким названием уже существует!", areaDoesntExist = "Зона с таким названием не существует!", areaInvalidType = "Вы указали неверный тип зоны!", areaEditTip = "Нажмите, чтобы начать создание зоны. Щелкните правой кнопкой мыши, чтобы выйти.", areaFinishTip = "Нажмите еще раз, чтобы закончить создание зоны. Щелкните правой кнопкой мыши, чтобы вернуться.", areaRemoveTip = "Нажмите перезарядку, чтобы удалить зону, в которой вы находитесь.", areaDeleteConfirm = "Вы уверены, что хотите удалить зону \"%s\"?", areaDelete = "Удалить зону", cmdAreaEdit = "Вход в режим редактирования зоны." } ================================================ FILE: plugins/area/sh_plugin.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Areas" PLUGIN.author = "`impulse" PLUGIN.description = "Provides customizable area definitions." ix.area = ix.area or {} ix.area.types = ix.area.types or {} ix.area.properties = ix.area.properties or {} ix.area.stored = ix.area.stored or {} ix.config.Add("areaTickTime", 1, "How many seconds between each time a character's current area is calculated.", function(oldValue, newValue) if (SERVER) then timer.Adjust("ixAreaThink", newValue) end end, { data = {min = 0.1, max = 4}, category = "areas" } ) function ix.area.AddProperty(name, type, default, data) ix.area.properties[name] = { type = type, default = default, data = data or {} } end function ix.area.AddType(type, name) name = name or type -- only store localized strings on the client ix.area.types[type] = CLIENT and name or true end function PLUGIN:SetupAreaProperties() ix.area.AddType("area") ix.area.AddProperty("color", ix.type.color, ix.config.Get("color")) ix.area.AddProperty("display", ix.type.bool, true) end ix.util.Include("sv_plugin.lua") ix.util.Include("cl_plugin.lua") ix.util.Include("sv_hooks.lua") ix.util.Include("cl_hooks.lua") -- return world center, local min, and local max from world start/end positions function PLUGIN:GetLocalAreaPosition(startPosition, endPosition) local center = LerpVector(0.5, startPosition, endPosition) local min = WorldToLocal(startPosition, angle_zero, center, angle_zero) local max = WorldToLocal(endPosition, angle_zero, center, angle_zero) return center, min, max end do local COMMAND = {} COMMAND.description = "@cmdAreaEdit" COMMAND.adminOnly = true function COMMAND:OnRun(client) client:SetWepRaised(false) net.Start("ixAreaEditStart") net.Send(client) end ix.command.Add("AreaEdit", COMMAND) end do local PLAYER = FindMetaTable("Player") -- returns the current area the player is in, or the last valid one if the player is not in an area function PLAYER:GetArea() return self.ixArea end -- returns true if the player is in any area, this does not use the last valid area like GetArea does function PLAYER:IsInArea() return self.ixInArea end end ================================================ FILE: plugins/area/sv_hooks.lua ================================================ function PLUGIN:LoadData() hook.Run("SetupAreaProperties") ix.area.stored = self:GetData() or {} timer.Create("ixAreaThink", ix.config.Get("areaTickTime"), 0, function() self:AreaThink() end) end function PLUGIN:SaveData() self:SetData(ix.area.stored) end function PLUGIN:PlayerInitialSpawn(client) timer.Simple(1, function() if (IsValid(client)) then local json = util.TableToJSON(ix.area.stored) local compressed = util.Compress(json) local length = compressed:len() net.Start("ixAreaSync") net.WriteUInt(length, 32) net.WriteData(compressed, length) net.Send(client) end end) end function PLUGIN:PlayerLoadedCharacter(client) client.ixArea = "" client.ixInArea = nil end function PLUGIN:PlayerSpawn(client) client.ixArea = "" client.ixInArea = nil end function PLUGIN:AreaThink() for _, client in player.Iterator() do local character = client:GetCharacter() if (!client:Alive() or !character) then continue end local overlappingBoxes = {} local position = client:GetPos() + client:OBBCenter() for id, info in pairs(ix.area.stored) do if (position:WithinAABox(info.startPosition, info.endPosition)) then overlappingBoxes[#overlappingBoxes + 1] = id end end if (#overlappingBoxes > 0) then local oldID = client:GetArea() local id = overlappingBoxes[1] if (oldID != id) then hook.Run("OnPlayerAreaChanged", client, client.ixArea, id) client.ixArea = id end client.ixInArea = true else client.ixInArea = false end end end function PLUGIN:OnPlayerAreaChanged(client, oldID, newID) net.Start("ixAreaChanged") net.WriteString(oldID) net.WriteString(newID) net.Send(client) end net.Receive("ixAreaAdd", function(length, client) if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then return end local id = net.ReadString() local type = net.ReadString() local startPosition, endPosition = net.ReadVector(), net.ReadVector() local properties = net.ReadTable() if (!ix.area.types[type]) then client:NotifyLocalized("areaInvalidType") return end if (ix.area.stored[id]) then client:NotifyLocalized("areaAlreadyExists") return end for k, v in pairs(properties) do if (!isstring(k) or !ix.area.properties[k]) then continue end properties[k] = ix.util.SanitizeType(ix.area.properties[k].type, v) end ix.area.Create(id, type, startPosition, endPosition, nil, properties) ix.log.Add(client, "areaAdd", id) end) net.Receive("ixAreaRemove", function(length, client) if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then return end local id = net.ReadString() if (!ix.area.stored[id]) then client:NotifyLocalized("areaDoesntExist") return end ix.area.Remove(id) ix.log.Add(client, "areaRemove", id) end) ================================================ FILE: plugins/area/sv_plugin.lua ================================================ util.AddNetworkString("ixAreaSync") util.AddNetworkString("ixAreaAdd") util.AddNetworkString("ixAreaRemove") util.AddNetworkString("ixAreaChanged") util.AddNetworkString("ixAreaEditStart") util.AddNetworkString("ixAreaEditEnd") ix.log.AddType("areaAdd", function(client, name) return string.format("%s has added area \"%s\".", client:Name(), tostring(name)) end) ix.log.AddType("areaRemove", function(client, name) return string.format("%s has removed area \"%s\".", client:Name(), tostring(name)) end) local function SortVector(first, second) return Vector(math.min(first.x, second.x), math.min(first.y, second.y), math.min(first.z, second.z)), Vector(math.max(first.x, second.x), math.max(first.y, second.y), math.max(first.z, second.z)) end function ix.area.Create(name, type, startPosition, endPosition, bNoReplicate, properties) local min, max = SortVector(startPosition, endPosition) ix.area.stored[name] = { type = type or "area", startPosition = min, endPosition = max, bNoReplicate = bNoReplicate, properties = properties } -- network to clients if needed if (!bNoReplicate) then net.Start("ixAreaAdd") net.WriteString(name) net.WriteString(type) net.WriteVector(startPosition) net.WriteVector(endPosition) net.WriteTable(properties) net.Broadcast() end end function ix.area.Remove(name, bNoReplicate) ix.area.stored[name] = nil -- network to clients if needed if (!bNoReplicate) then net.Start("ixAreaRemove") net.WriteString(name) net.Broadcast() end end ================================================ FILE: plugins/chatbox/derma/cl_chatbox.lua ================================================ local PLUGIN = PLUGIN local animationTime = 0.5 local chatBorder = 32 local sizingBorder = 20 local maxChatEntries = 100 -- called when a markup object should paint its text local function PaintMarkupOverride(text, font, x, y, color, alignX, alignY, alpha) alpha = alpha or 255 if (ix.option.Get("chatOutline", false)) then -- outlined background for even more visibility draw.SimpleTextOutlined(text, font, x, y, ColorAlpha(color, alpha), alignX, alignY, 1, Color(0, 0, 0, alpha)) else -- background for easier reading surface.SetTextPos(x + 1, y + 1) surface.SetTextColor(0, 0, 0, alpha) surface.SetFont(font) surface.DrawText(text) surface.SetTextPos(x, y) surface.SetTextColor(color.r, color.g, color.b, alpha) surface.SetFont(font) surface.DrawText(text) end end -- chat message local PANEL = {} AccessorFunc(PANEL, "fadeDelay", "FadeDelay", FORCE_NUMBER) AccessorFunc(PANEL, "fadeDuration", "FadeDuration", FORCE_NUMBER) function PANEL:Init() self.text = "" self.alpha = 255 self.fadeDelay = 15 self.fadeDuration = 5 end function PANEL:SetMarkup(text) self.text = text self.markup = ix.markup.Parse(self.text, self:GetWide()) self.markup.onDrawText = PaintMarkupOverride self:SetTall(self.markup:GetHeight()) timer.Simple(self.fadeDelay, function() if (!IsValid(self)) then return end self:CreateAnimation(self.fadeDuration, { index = 3, target = {alpha = 0} }) end) end function PANEL:PerformLayout(width, height) if ((IsValid(ix.gui.chat) and ix.gui.chat.bSizing) or width == self.markup:GetWidth()) then return end self.markup = ix.markup.Parse(self.text, width) self.markup.onDrawText = PaintMarkupOverride self:SetTall(self.markup:GetHeight()) end function PANEL:Paint(width, height) local newAlpha -- we'll want to hide the chat while some important menus are open if (IsValid(ix.gui.characterMenu)) then newAlpha = math.min(255 - ix.gui.characterMenu.currentAlpha, self.alpha) elseif (IsValid(ix.gui.menu)) then newAlpha = math.min(255 - ix.gui.menu.currentAlpha, self.alpha) elseif (ix.gui.chat:GetActive()) then newAlpha = math.max(ix.gui.chat.alpha, self.alpha) else newAlpha = self.alpha end if (newAlpha < 1) then return end self.markup:draw(0, 0, nil, nil, newAlpha) end vgui.Register("ixChatMessage", PANEL, "Panel") -- chatbox tab button PANEL = {} AccessorFunc(PANEL, "bActive", "Active", FORCE_BOOL) AccessorFunc(PANEL, "bUnread", "Unread", FORCE_BOOL) function PANEL:Init() self:SetFont("ixChatFont") self:SetContentAlignment(5) self.unreadAlpha = 0 end function PANEL:SetUnread(bValue) self.bUnread = bValue self:CreateAnimation(animationTime, { index = 4, target = {unreadAlpha = bValue and 1 or 0}, easing = "outQuint" }) end function PANEL:SizeToContents() local width, height = self:GetContentSize() self:SetSize(width + 12, height + 6) end function PANEL:Paint(width, height) derma.SkinFunc("PaintChatboxTabButton", self, width, height) end vgui.Register("ixChatboxTabButton", PANEL, "DButton") -- chatbox tab panel -- holds all tab buttons and corresponding history panels PANEL = {} function PANEL:Init() -- holds all tab buttons self.buttons = self:Add("Panel") self.buttons:Dock(TOP) self.buttons:DockPadding(1, 1, 0, 0) self.buttons.OnMousePressed = ix.util.Bind(ix.gui.chat, ix.gui.chat.OnMousePressed) -- we want mouse events to fall through self.buttons.OnMouseReleased = ix.util.Bind(ix.gui.chat, ix.gui.chat.OnMouseReleased) self.buttons.Paint = function(_, width, height) derma.SkinFunc("PaintChatboxTabs", self, width, height) end self.tabs = {} end function PANEL:GetTabs() return self.tabs end function PANEL:AddTab(id, filter) local button = self.buttons:Add("ixChatboxTabButton") button:Dock(LEFT) button:SetText(id) -- display name is also the ID button:SetActive(false) button:SetMouseInputEnabled(true) button:SizeToContents() button.DoClick = function(this) self:SetActiveTab(this:GetText()) end local panel = self:Add("ixChatboxHistory") panel:SetButton(button) panel:SetID(id) panel:Dock(FILL) panel:SetVisible(false) panel:SetFilter(filter or {}) button.DoRightClick = function(this) ix.gui.chat:OnTabRightClick(this, panel, panel:GetID()) end self.tabs[id] = panel return panel end function PANEL:RemoveTab(id) local tab = self.tabs[id] if (!tab) then return end tab:GetButton():Remove() tab:Remove() self.tabs[id] = nil -- add default tab if we don't have any tabs left if (table.IsEmpty(self.tabs)) then self:AddTab(L("chat"), {}) self:SetActiveTab(L("chat")) elseif (id == self:GetActiveTabID()) then -- set a different active tab if we've removed a tab that is currently active self:SetActiveTab(next(self.tabs)) end end function PANEL:RenameTab(id, newID) local tab = self.tabs[id] if (!tab) then return end tab:GetButton():SetText(newID) tab:GetButton():SizeToContents() tab:SetID(newID) self.tabs[id] = nil self.tabs[newID] = tab if (id == self:GetActiveTabID()) then self:SetActiveTab(newID) end end function PANEL:SetActiveTab(id) local tab = self.tabs[id] if (!tab) then error("attempted to set non-existent active tab") end for _, v in ipairs(self.buttons:GetChildren()) do v:SetActive(v:GetText() == id) end for _, v in pairs(self.tabs) do v:SetVisible(v:GetID() == id) end tab:GetButton():SetUnread(false) self.activeTab = id self:OnTabChanged(tab) end function PANEL:GetActiveTabID() return self.activeTab end function PANEL:GetActiveTab() return self.tabs[self.activeTab] end -- called when the active tab is changed -- `panel` is the corresponding history panel function PANEL:OnTabChanged(panel) end vgui.Register("ixChatboxTabs", PANEL, "EditablePanel") -- chatbox history panel -- holds individual messages in a scrollable panel PANEL = {} AccessorFunc(PANEL, "filter", "Filter") -- blacklist of message classes AccessorFunc(PANEL, "id", "ID", FORCE_STRING) AccessorFunc(PANEL, "button", "Button") -- button panel that this panel corresponds to function PANEL:Init() self:DockMargin(4, 2, 4, 4) -- smaller top margin to help blend tab button/history panel transition self:SetPaintedManually(true) local bar = self:GetVBar() bar:SetWide(0) self.entries = {} self.filter = {} end DEFINE_BASECLASS("Panel") -- DScrollPanel doesn't have SetVisible member function PANEL:SetVisible(bState) self:GetCanvas():SetVisible(bState) BaseClass.SetVisible(self, bState) end DEFINE_BASECLASS("DScrollPanel") function PANEL:PerformLayoutInternal() local bar = self:GetVBar() local bScroll = !ix.gui.chat:GetActive() or bar.Scroll == bar.CanvasSize -- only scroll when we're not at the bottom/inactive BaseClass.PerformLayoutInternal(self) if (bScroll) then self:ScrollToBottom() end end function PANEL:ScrollToBottom() local bar = self:GetVBar() bar:SetScroll(bar.CanvasSize) end -- adds a line of text as described by its elements function PANEL:AddLine(elements, bShouldScroll) -- table.concat is faster than regular string concatenation where there are lots of strings to concatenate local buffer = { "" } if (ix.option.Get("chatTimestamps", false)) then buffer[#buffer + 1] = "(" if (ix.option.Get("24hourTime", false)) then buffer[#buffer + 1] = os.date("%H:%M") else buffer[#buffer + 1] = os.date("%I:%M %p") end buffer[#buffer + 1] = ") " end if (CHAT_CLASS) then buffer[#buffer + 1] = " ", texture, v:Width(), v:Height()) end elseif (istable(v) and v.r and v.g and v.b) then buffer[#buffer + 1] = string.format("", v.r, v.g, v.b) elseif (type(v) == "Player") then local color = team.GetColor(v:Team()) buffer[#buffer + 1] = string.format("%s", color.r, color.g, color.b, v:GetName():gsub("<", "<"):gsub(">", ">")) else buffer[#buffer + 1] = tostring(v):gsub("<", "<"):gsub(">", ">"):gsub("%b**", function(value) local inner = value:utf8sub(2, -2) if (inner:find("%S")) then return "" .. value:utf8sub(2, -2) .. "" end end) end end local panel = self:Add("ixChatMessage") panel:Dock(TOP) panel:InvalidateParent(true) panel:SetMarkup(table.concat(buffer)) if (#self.entries >= maxChatEntries) then local oldPanel = table.remove(self.entries, 1) if (IsValid(oldPanel)) then oldPanel:Remove() end end self.entries[#self.entries + 1] = panel return panel end vgui.Register("ixChatboxHistory", PANEL, "DScrollPanel") PANEL = {} DEFINE_BASECLASS("DTextEntry") function PANEL:Init() self:SetFont("ixChatFont") self:SetUpdateOnType(true) self:SetHistoryEnabled(true) self.History = ix.chat.history self.m_bLoseFocusOnClickAway = false end function PANEL:SetFont(font) BaseClass.SetFont(self, font) surface.SetFont(font) local _, height = surface.GetTextSize("W@") self:SetTall(height + 8) end function PANEL:AllowInput(newCharacter) local text = self:GetText() local maxLength = ix.config.Get("chatMax") -- we can't check for the proper length using utf-8 since AllowInput is called for single bytes instead of full characters if (string.len(text .. newCharacter) > maxLength) then surface.PlaySound("common/talk.wav") return true end end function PANEL:Think() local text = self:GetText() local maxLength = ix.config.Get("chatMax", 256) if (text:utf8len() > maxLength) then local newText = text:utf8sub(0, maxLength) self:SetText(newText) self:SetCaretPos(newText:utf8len()) end end function PANEL:Paint(width, height) derma.SkinFunc("PaintChatboxEntry", self, width, height) end vgui.Register("ixChatboxEntry", PANEL, "DTextEntry") -- chatbox additional command info panel PANEL = {} AccessorFunc(PANEL, "text", "Text", FORCE_STRING) AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") AccessorFunc(PANEL, "textColor", "TextColor") function PANEL:Init() self.text = "" self.padding = 4 self.currentWidth = 0 self.currentMargin = 0 self.backgroundColor = ix.config.Get("color") self.textColor = color_white self:SetWide(0) self:DockMargin(0, 0, 0, 0) end function PANEL:SetText(text) self:SetVisible(true) if (!isstring(text) or text == "") then self:CreateAnimation(animationTime, { index = 9, easing = "outQuint", target = { currentWidth = 0, currentMargin = 0 }, Think = function(animation, panel) panel:SetWide(panel.currentWidth) panel:DockMargin(0, 0, panel.currentMargin, 0) end, OnComplete = function(animation, panel) panel:SetVisible(false) self.text = "" end }) else text = tostring(text) surface.SetFont("ixChatFont") local textWidth = surface.GetTextSize(text) self:CreateAnimation(animationTime, { index = 9, easing = "outQuint", target = { currentWidth = textWidth + self.padding * 2, currentMargin = 4 }, Think = function(animation, panel) panel:SetWide(panel.currentWidth) panel:DockMargin(0, 0, panel.currentMargin, 0) end, }) self.text = text end end function PANEL:Paint(width, height) derma.SkinFunc("DrawChatboxPrefixBox", self, width, height) surface.SetFont("ixChatFont") local textWidth, textHeight = surface.GetTextSize(self.text) surface.SetTextColor(self.textColor) surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5) surface.DrawText(self.text) end vgui.Register("ixChatboxPrefix", PANEL, "Panel") -- chatbox command preview panel PANEL = {} DEFINE_BASECLASS("Panel") AccessorFunc(PANEL, "targetHeight", "TargetHeight", FORCE_NUMBER) AccessorFunc(PANEL, "command", "Command", FORCE_STRING) function PANEL:Init() self:SetTall(0) self:SetVisible(false, true) self.height = 0 self.targetHeight = 16 self.margin = 0 self.command = "" end function PANEL:SetCommand(command) -- if we're setting it to an empty command, then we'll hold the reference to the old command table to render it for the -- fade out animation if (command == "") then self.command = "" ix.chat.currentCommand = "" return end local commandTable = ix.command.list[command] if (!commandTable) then return end self.command = command self.commandTable = commandTable self.arguments = {} ix.chat.currentCommand = command:lower() end function PANEL:UpdateArguments(text) if (self.command == "") then ix.chat.currentArguments = {} return end local commandName = text:match("(/(%w+)%s)") or self.command -- we could be using a chat class prefix and not a proper command local givenArguments = ix.command.ExtractArgs(text:utf8sub(commandName:utf8len())) local commandArguments = self.commandTable.arguments or {} local arguments = {} -- we want to concat any text types so they show up as one argument at the end of the list, this is so the argument -- highlighting is accurate since ExtractArgs will not account because it has no type context for k, v in ipairs(givenArguments) do if (k == #commandArguments) then arguments[#arguments + 1] = table.concat(givenArguments, " ", k) break end arguments[#arguments + 1] = v end self.arguments = arguments ix.chat.currentArguments = table.Copy(arguments) end -- returns the target SetVisible value function PANEL:IsOpen() return self.bOpen end function PANEL:SetVisible(bValue, bForce) if (bForce) then BaseClass.SetVisible(self, bValue) return end BaseClass.SetVisible(self, true) -- make sure this panel is visible during animation self.bOpen = bValue self:CreateAnimation(animationTime * 0.5, { index = 5, target = { height = bValue and self.targetHeight or 0, margin = bValue and 4 or 0 }, easing = "outQuint", Think = function(animation, panel) panel:SetTall(math.ceil(panel.height)) panel:DockMargin(4, 0, 4, math.ceil(panel.margin)) end, OnComplete = function(animation, panel) BaseClass.SetVisible(panel, bValue) end }) end function PANEL:Paint(width, height) local command = self.commandTable if (!command) then return end local color = ix.config.Get("color") surface.SetFont("ixChatFont") -- command name local x = derma.SkinFunc("DrawChatboxPreviewBox", 0, 0, "/" .. command.name) + 6 -- command arguments if (istable(command.arguments)) then for k, v in ipairs(command.arguments) do local bOptional = bit.band(v, ix.type.optional) > 0 local type = bOptional and bit.bxor(v, ix.type.optional) or v x = x + derma.SkinFunc( "DrawChatboxPreviewBox", x, 0, -- draw text in format of or [name: type] if it's optional string.format(bOptional and "[%s: %s]" or "<%s: %s>", command.argumentNames[k], ix.type[type]), -- fill in the color for arguments that are before the one the user is currently typing, otherwise draw a faded -- color instead (optional arguments will not have any background color unless it's been filled out by user) (k <= #self.arguments) and color or (bOptional and Color(0, 0, 0, 66) or ColorAlpha(color, 100)) ) + 6 end end end vgui.Register("ixChatboxPreview", PANEL, "Panel") -- chatbox autocomplete panel -- holds and displays similar commands based on the textentry PANEL = {} DEFINE_BASECLASS("Panel") AccessorFunc(PANEL, "maxEntries", "MaxEntries", FORCE_NUMBER) function PANEL:Init() self:SetVisible(false, true) self:SetMouseInputEnabled(true) self.maxEntries = 20 self.currentAlpha = 0 self.commandIndex = 0 -- currently selected entry in command list self.commands = {} self.commandPanels = {} end function PANEL:GetCommands() return self.commands end function PANEL:IsOpen() return self.bOpen end function PANEL:SetVisible(bValue, bForce) if (bForce) then BaseClass.SetVisible(self, bValue) return end BaseClass.SetVisible(self, true) -- make sure this panel is visible during animation self.bOpen = bValue self:CreateAnimation(animationTime, { index = 6, target = { currentAlpha = bValue and 255 or 0 }, easing = "outQuint", Think = function(animation, panel) panel:SetAlpha(math.ceil(panel.currentAlpha)) end, OnComplete = function(animation, panel) BaseClass.SetVisible(panel, bValue) if (!bValue) then self.commands = {} end end }) end function PANEL:Update(text) local commands = ix.command.FindAll(text, true, true, true) self.commandIndex = 0 -- reset the command index because the command list could be different self.commands = {} for _, v in ipairs(self.commandPanels) do v:Remove() end self.commandPanels = {} -- manually loop over the found commands so we can ignore commands the user doesn't have access to local i = 1 local bSelected -- just to make sure we don't reset it during the loop for whatever reason for _, v in ipairs(commands) do -- @todo chat classes aren't checked since they're done through the class's OnCanSay callback if (v.OnCheckAccess and !v:OnCheckAccess(LocalPlayer())) then continue end local panel = self:Add("ixChatboxAutocompleteEntry") panel:SetCommand(v) if (!bSelected and text:utf8lower():utf8sub(1, v.uniqueID:utf8len()) == v.uniqueID) then panel:SetHighlighted(true) self.commandIndex = i bSelected = true end self.commandPanels[i] = panel self.commands[i] = v if (i == self.maxEntries) then break end i = i + 1 end end -- selects the next entry in the autocomplete if possible and returns the text that should replace the textentry function PANEL:SelectNext() -- wrap back to beginning if we're past the end if (self.commandIndex == #self.commands) then self.commandIndex = 1 else self.commandIndex = self.commandIndex + 1 end for k, v in ipairs(self.commandPanels) do if (k == self.commandIndex) then v:SetHighlighted(true) self:ScrollToChild(v) else v:SetHighlighted(false) end end return "/" .. self.commands[self.commandIndex].uniqueID end function PANEL:Paint(width, height) ix.util.DrawBlur(self) surface.SetDrawColor(0, 0, 0, 200) surface.DrawRect(0, 0, width, height) end vgui.Register("ixChatboxAutocomplete", PANEL, "DScrollPanel") -- autocomplete entry PANEL = {} AccessorFunc(PANEL, "bSelected", "Highlighted", FORCE_BOOL) function PANEL:Init() self:Dock(TOP) self.name = self:Add("DLabel") self.name:Dock(TOP) self.name:DockMargin(4, 4, 0, 0) self.name:SetContentAlignment(4) self.name:SetFont("ixChatFont") self.name:SetTextColor(ix.config.Get("color")) self.name:SetExpensiveShadow(1, color_black) self.description = self:Add("DLabel") self.description:Dock(BOTTOM) self.description:DockMargin(4, 4, 0, 4) self.description:SetContentAlignment(4) self.description:SetFont("ixChatFont") self.description:SetTextColor(color_white) self.description:SetExpensiveShadow(1, color_black) self.highlightAlpha = 0 end function PANEL:SetHighlighted(bValue) self:CreateAnimation(animationTime * 2, { index = 7, target = {highlightAlpha = bValue and 1 or 0}, easing = "outQuint" }) self.bHighlighted = true end function PANEL:SetCommand(command) local description = command:GetDescription() self.name:SetText("/" .. command.name) if (description and description != "") then self.description:SetText(command:GetDescription()) else self.description:SetVisible(false) end self:SizeToContents() self.command = command end function PANEL:SizeToContents() local bDescriptionVisible = self.description:IsVisible() local _, height = self.name:GetContentSize() self.name:SetTall(height) if (bDescriptionVisible) then _, height = self.description:GetContentSize() self.description:SetTall(height) else self.description:SetTall(0) end self:SetTall(self.name:GetTall() + self.description:GetTall() + (bDescriptionVisible and 12 or 8)) end function PANEL:Paint(width, height) derma.SkinFunc("PaintChatboxAutocompleteEntry", self, width, height) end vgui.Register("ixChatboxAutocompleteEntry", PANEL, "Panel") -- main chatbox panel -- this contains the text entry, tab sheets, and callbacks for other panel events PANEL = {} AccessorFunc(PANEL, "bActive", "Active", FORCE_BOOL) function PANEL:Init() ix.gui.chat = self self:SetSize(self:GetDefaultSize()) self:SetPos(self:GetDefaultPosition()) local entryPanel = self:Add("Panel") entryPanel:SetZPos(1) entryPanel:Dock(BOTTOM) entryPanel:DockMargin(4, 0, 4, 4) self.entry = entryPanel:Add("ixChatboxEntry") self.entry:Dock(FILL) self.entry.OnValueChange = ix.util.Bind(self, self.OnTextChanged) self.entry.OnKeyCodeTyped = ix.util.Bind(self, self.OnKeyCodeTyped) self.entry.OnEnter = ix.util.Bind(self, self.OnMessageSent) self.prefix = entryPanel:Add("ixChatboxPrefix") self.prefix:Dock(LEFT) self.preview = self:Add("ixChatboxPreview") self.preview:SetZPos(2) -- ensure the preview is docked above the text entry self.preview:Dock(BOTTOM) self.preview:SetTargetHeight(self.entry:GetTall()) self.tabs = self:Add("ixChatboxTabs") self.tabs:Dock(FILL) self.tabs.OnTabChanged = ix.util.Bind(self, self.OnTabChanged) self.autocomplete = self.tabs:Add("ixChatboxAutocomplete") self.autocomplete:Dock(FILL) self.autocomplete:DockMargin(4, 3, 4, 4) -- top margin is 3 to account for tab 1px border self.autocomplete:SetZPos(3) self.alpha = 0 self:SetActive(false) -- luacheck: globals chat chat.GetChatBoxPos = function() return self:GetPos() end chat.GetChatBoxSize = function() return self:GetSize() end end function PANEL:GetDefaultSize() return ScrW() * 0.4, ScrH() * 0.375 end function PANEL:GetDefaultPosition() return chatBorder, ScrH() - self:GetTall() - chatBorder end DEFINE_BASECLASS("Panel") function PANEL:SetAlpha(amount, duration) self:CreateAnimation(duration or animationTime, { index = 1, target = {alpha = amount}, easing = "outQuint", Think = function(animation, panel) BaseClass.SetAlpha(panel, panel.alpha) end }) end function PANEL:SizingInBounds() local screenX, screenY = self:LocalToScreen(0, 0) local mouseX, mouseY = gui.MousePos() return mouseX > screenX + self:GetWide() - sizingBorder and mouseY > screenY + self:GetTall() - sizingBorder end function PANEL:DraggingInBounds() local _, screenY = self:LocalToScreen(0, 0) local mouseY = gui.MouseY() return mouseY > screenY and mouseY < screenY + self.tabs.buttons:GetTall() end function PANEL:SetActive(bActive) if (bActive) then self:SetAlpha(255) self:MakePopup() self.entry:RequestFocus() input.SetCursorPos(self:LocalToScreen(-1, -1)) hook.Run("StartChat") self.prefix:SetText(hook.Run("GetChatPrefixInfo", "")) else -- make sure we aren't still sizing/dragging anything if (self.bSizing or self.DragOffset) then self:OnMouseReleased(MOUSE_LEFT) end self:SetAlpha(0) self:SetMouseInputEnabled(false) self:SetKeyboardInputEnabled(false) self.autocomplete:SetVisible(false) self.preview:SetVisible(false) self.entry:SetText("") self.preview:SetCommand("") self.prefix:SetText(hook.Run("GetChatPrefixInfo", "")) CloseDermaMenus() gui.EnableScreenClicker(false) hook.Run("FinishChat") end local tab = self.tabs:GetActiveTab() if (tab) then -- we'll scroll to bottom even if we're opening since the SetVisible for the textentry will shift things a bit tab:ScrollToBottom() end self.bActive = tobool(bActive) end function PANEL:SetupTabs(tabs) if (!tabs or table.IsEmpty(tabs)) then self.tabs:AddTab(L("chat"), {}) self.tabs:SetActiveTab(L("chat")) return end for id, filter in pairs(tabs) do self.tabs:AddTab(id, filter) end self.tabs:SetActiveTab(next(tabs)) end function PANEL:SetupPosition(info) local x, y, width, height if (!istable(info)) then x, y = self:GetDefaultPosition() width, height = self:GetDefaultSize() else -- screen size may have changed so we'll need to clamp the values width = math.Clamp(info[3], 32, ScrW() - chatBorder * 2) height = math.Clamp(info[4], 32, ScrH() - chatBorder * 2) x = math.Clamp(info[1], 0, ScrW() - width) y = math.Clamp(info[2], 0, ScrH() - height) end self:SetSize(width, height) self:SetPos(x, y) PLUGIN:SavePosition() end function PANEL:OnMousePressed(key) if (key == MOUSE_RIGHT) then local menu = DermaMenu() menu:AddOption(L("chatNewTab"), function() if (IsValid(ix.gui.chatTabCustomize)) then ix.gui.chatTabCustomize:Remove() end local panel = vgui.Create("ixChatboxTabCustomize") panel.OnTabCreated = ix.util.Bind(self, self.OnTabCreated) end) menu:AddOption(L("chatMarkRead"), function() for _, v in pairs(self.tabs:GetTabs()) do v:GetButton():SetUnread(false) end end) menu:AddSpacer() menu:AddOption(L("chatReset"), function() local x, y = self:GetDefaultPosition() local width, height = self:GetDefaultSize() self:SetSize(width, height) self:SetPos(x, y) ix.option.Set("chatPosition", "") hook.Run("ChatboxPositionChanged", x, y, width, height) end) menu:AddOption(L("chatResetTabs"), function() for id, _ in pairs(self.tabs:GetTabs()) do self.tabs:RemoveTab(id) end ix.option.Set("chatTabs", "") end) menu:Open() menu:MakePopup() return end if (key != MOUSE_LEFT) then return end -- capture the mouse if we're in bounds for sizing this panel if (self:SizingInBounds()) then self.bSizing = true self:MouseCapture(true) elseif (self:DraggingInBounds()) then local mouseX, mouseY = self:ScreenToLocal(gui.MousePos()) -- mouse offset relative to the panel self.DragOffset = {mouseX, mouseY} self:MouseCapture(true) end end function PANEL:OnMouseReleased() self:MouseCapture(false) self:SetCursor("arrow") -- save new position/size if we were dragging/resizing if (self.bSizing or self.DragOffset) then PLUGIN:SavePosition() self.bSizing = nil self.DragOffset = nil -- resize chat messages to fit new width self:InvalidateChildren(true) local x, y = self:GetPos() local width, height = self:GetSize() hook.Run("ChatboxPositionChanged", x, y, width, height) end end function PANEL:Think() if (!self.bActive) then return end local mouseX = math.Clamp(gui.MouseX(), 0, ScrW()) local mouseY = math.Clamp(gui.MouseY(), 0, ScrH()) if (self.bSizing) then local x, y = self:GetPos() local width = math.Clamp(mouseX - x, chatBorder, ScrW() - chatBorder * 2) local height = math.Clamp(mouseY - y, chatBorder, ScrH() - chatBorder * 2) self:SetSize(width, height) self:SetCursor("sizenwse") elseif (self.DragOffset) then local x = math.Clamp(mouseX - self.DragOffset[1], 0, ScrW() - self:GetWide()) local y = math.Clamp(mouseY - self.DragOffset[2], 0, ScrH() - self:GetTall()) self:SetPos(x, y) elseif (self:SizingInBounds()) then self:SetCursor("sizenwse") elseif (self:DraggingInBounds()) then -- we have to set the cursor on the list panel since that's the actual hovered panel self.tabs.buttons:SetCursor("sizeall") else self:SetCursor("arrow") end end function PANEL:Paint(width, height) local tab = self.tabs:GetActiveTab() local alpha = self:GetAlpha() derma.SkinFunc("PaintChatboxBackground", self, width, height) if (tab) then -- manually paint active tab since messages handle their own alpha lifetime surface.SetAlphaMultiplier(1) tab:PaintManual() surface.SetAlphaMultiplier(alpha / 255) end if (alpha > 0) then hook.Run("PostChatboxDraw", width, height, self:GetAlpha()) end end -- get the command of the current chat class in the textentry if possible function PANEL:GetTextEntryChatClass(text) text = text or self.entry:GetText() local chatType = ix.chat.Parse(LocalPlayer(), text, true) if (chatType and chatType != "ic") then -- OOC is the only one with two slashes as its prefix, so we'll make a special case for it here if (chatType == "ooc") then return "ooc" end local class = ix.chat.classes[chatType] if (istable(class.prefix)) then for _, v in ipairs(class.prefix) do if (v:utf8sub(1, 1) == "/") then return v:utf8sub(2):utf8lower() end end elseif (class.prefix:utf8sub(1, 1) == "/") then return class.prefix:utf8sub(2):utf8lower() end end end -- chatbox panel hooks -- called when the textentry value changes function PANEL:OnTextChanged(text) hook.Run("ChatTextChanged", text) local preview = self.preview local autocomplete = self.autocomplete local chatClassCommand = self:GetTextEntryChatClass(text) self.prefix:SetText(hook.Run("GetChatPrefixInfo", text)) if (chatClassCommand) then preview:SetCommand(chatClassCommand) preview:SetVisible(true) preview:UpdateArguments(text) autocomplete:SetVisible(false) return end local start, _, command = text:find("(/(%w+)%s)") command = ix.command.list[tostring(command):utf8sub(2, tostring(command):utf8len() - 1):utf8lower()] -- update preview if we've found a command if (start == 1 and command) then preview:SetCommand(command.uniqueID) preview:SetVisible(true) preview:UpdateArguments(text) -- we don't need the autocomplete because we have a command already typed out autocomplete:SetVisible(false) return -- if there's a slash then we're probably going to be (or are currently) typing out a command elseif (text:utf8sub(1, 1) == "/") then command = text:match("(/(%w+))") or "/" preview:SetVisible(false) -- we don't have a valid command yet autocomplete:Update(command:utf8sub(2)) autocomplete:SetVisible(true) return end if (preview:GetCommand() != "") then preview:SetCommand("") preview:SetVisible(false) end if (autocomplete:IsVisible()) then autocomplete:SetVisible(false) end end DEFINE_BASECLASS("DTextEntry") function PANEL:OnKeyCodeTyped(key) if (key == KEY_TAB) then if (self.autocomplete:IsOpen() and #self.autocomplete:GetCommands() > 0) then local newText = self.autocomplete:SelectNext() self.entry:SetText(newText) self.entry:SetCaretPos(newText:utf8len()) end return true end return BaseClass.OnKeyCodeTyped(self.entry, key) end -- called when player types something and presses enter in the textentry function PANEL:OnMessageSent() local text = self.entry:GetText() if (text:find("%S")) then local lastEntry = ix.chat.history[#ix.chat.history] -- only add line to textentry history if it isn't the same message if (lastEntry != text) then if (#ix.chat.history >= 20) then table.remove(ix.chat.history, 1) end ix.chat.history[#ix.chat.history + 1] = text end net.Start("ixChatMessage") net.WriteString(text) net.SendToServer() end self:SetActive(false) -- textentry is set to "" in SetActive end -- called when the player changes the currently active tab function PANEL:OnTabChanged(panel) panel:InvalidateLayout(true) panel:ScrollToBottom() end -- called when the player creates a new tab function PANEL:OnTabCreated(id, filter) self.tabs:AddTab(id, filter) PLUGIN:SaveTabs() end -- called when the player updates a tab's filter function PANEL:OnTabUpdated(id, filter, newID) local tab = self.tabs:GetTabs()[id] if (!tab) then return end tab:SetFilter(filter) self.tabs:RenameTab(id, newID) PLUGIN:SaveTabs() end -- called when a tab's button was right-clicked function PANEL:OnTabRightClick(button, tab, id) local menu = DermaMenu() menu:AddOption(L("chatCustomize"), function() if (IsValid(ix.gui.chatTabCustomize)) then ix.gui.chatTabCustomize:Remove() end local panel = vgui.Create("ixChatboxTabCustomize") panel:PopulateFromTab(id, tab:GetFilter()) panel.OnTabUpdated = ix.util.Bind(self, self.OnTabUpdated) end) menu:AddSpacer() menu:AddOption(L("chatCloseTab"), function() self.tabs:RemoveTab(id) PLUGIN:SaveTabs() end) menu:Open() menu:MakePopup() -- HACK: mouse input doesn't work when created immediately after opening chatbox end -- called when a message needs to be added to applicable tabs function PANEL:AddMessage(...) local class = CHAT_CLASS and CHAT_CLASS.uniqueID or "notice" local activeTab = self.tabs:GetActiveTab() -- track whether or not the message was filtered out in the active tab local bShown = false if (activeTab and !activeTab:GetFilter()[class]) then activeTab:AddLine({...}, true) bShown = true end for _, v in pairs(self.tabs:GetTabs()) do if (v:GetID() == activeTab:GetID()) then continue -- we already added it to the active tab end if (!v:GetFilter()[class]) then v:AddLine({...}, true) -- mark other tabs as unread if we didn't show the message in the active tab if (!bShown) then v:GetButton():SetUnread(true) end end end if (bShown) then chat.PlaySound() end end vgui.Register("ixChatbox", PANEL, "EditablePanel") ================================================ FILE: plugins/chatbox/derma/cl_chatboxcustomize.lua ================================================ local PLUGIN = PLUGIN local PANEL = {} function PANEL:Init() ix.gui.chatTabCustomize = self self:SetTitle(L("chatNewTab")) self:SetSizable(true) self:SetSize(ScrW() * 0.5, ScrH() * 0.5) self.settings = self:Add("ixSettings") self.settings:Dock(FILL) self.settings:SetSearchEnabled(true) self.settings:AddCategory(L("chatAllowedClasses")) -- controls local controlsPanel = self:Add("Panel") controlsPanel:Dock(BOTTOM) controlsPanel:DockMargin(0, 4, 0, 0) controlsPanel:SetTall(32) self.create = controlsPanel:Add("DButton") self.create:SetText(L("create")) self.create:SizeToContents() self.create:Dock(FILL) self.create:DockMargin(0, 0, 4, 0) self.create.DoClick = ix.util.Bind(self, self.CreateClicked) local uncheckAll = controlsPanel:Add("DButton") uncheckAll:SetText(L("uncheckAll")) uncheckAll:SizeToContents() uncheckAll:Dock(RIGHT) uncheckAll.DoClick = function() self:SetAllValues(false) end local checkAll = controlsPanel:Add("DButton") checkAll:SetText(L("checkAll")) checkAll:SizeToContents() checkAll:Dock(RIGHT) checkAll:DockMargin(0, 0, 4, 0) checkAll.DoClick = function() self:SetAllValues(true) end -- chat class settings self.name = self.settings:AddRow(ix.type.string) self.name:SetText(L("chatTabName")) self.name:SetValue(L("chatNewTabTitle")) self.name:SetZPos(-1) for k, _ in SortedPairs(ix.chat.classes) do local panel = self.settings:AddRow(ix.type.bool, L("chatAllowedClasses")) panel:SetText(k) panel:SetValue(true, true) end self.settings:SizeToContents() self:Center() self:MakePopup() end function PANEL:PopulateFromTab(name, filter) self.tab = name self:SetTitle(L("chatCustomize")) self.create:SetText(L("update")) self.name:SetValue(name) for _, v in ipairs(self.settings:GetRows()) do if (filter[v:GetText()]) then v:SetValue(false, true) end end end function PANEL:SetAllValues(bValue) for _, v in ipairs(self.settings:GetRows()) do if (v == self.name) then continue end v:SetValue(tobool(bValue), true) end end function PANEL:CreateClicked() local name = self.tab and self.tab or self.name:GetValue() if (self.tab != self.name:GetValue() and PLUGIN:TabExists(self.name:GetValue())) then ix.util.Notify(L("chatTabExists")) return end local filter = {} for _, v in ipairs(self.settings:GetRows()) do -- we only want to add entries for classes we don't want shown if (!v:GetValue()) then filter[v:GetText()] = true end end if (self.tab) then self:OnTabUpdated(name, filter, self.name:GetValue()) else self:OnTabCreated(name, filter) end self:Remove() end function PANEL:OnTabCreated(id, filter) end function PANEL:OnTabUpdated(id, filter, newID) end vgui.Register("ixChatboxTabCustomize", PANEL, "DFrame") ================================================ FILE: plugins/chatbox/sh_plugin.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Chatbox" PLUGIN.author = "`impulse" PLUGIN.description = "Replaces the chatbox to enable customization, autocomplete, and useful info." if (CLIENT) then ix.chat.history = ix.chat.history or {} -- array of strings the player has entered into the chatbox ix.chat.currentCommand = "" ix.chat.currentArguments = {} ix.option.Add("chatNotices", ix.type.bool, false, { category = "chat" }) ix.option.Add("chatTimestamps", ix.type.bool, false, { category = "chat" }) ix.option.Add("chatFontScale", ix.type.number, 1, { category = "chat", min = 0.1, max = 2, decimals = 2, OnChanged = function() hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) PLUGIN:CreateChat() end }) ix.option.Add("chatOutline", ix.type.bool, false, { category = "chat" }) -- tabs and their respective filters ix.option.Add("chatTabs", ix.type.string, "", { category = "chat", hidden = function() return true end }) -- chatbox size and position ix.option.Add("chatPosition", ix.type.string, "", { category = "chat", hidden = function() return true end }) function PLUGIN:CreateChat() if (IsValid(self.panel)) then self.panel:Remove() end self.panel = vgui.Create("ixChatbox") self.panel:SetupTabs(util.JSONToTable(ix.option.Get("chatTabs", ""))) self.panel:SetupPosition(util.JSONToTable(ix.option.Get("chatPosition", ""))) hook.Run("ChatboxCreated") end function PLUGIN:TabExists(id) if (!IsValid(self.panel)) then return false end return self.panel.tabs:GetTabs()[id] != nil end function PLUGIN:SaveTabs() local tabs = {} for id, panel in pairs(self.panel.tabs:GetTabs()) do tabs[id] = panel:GetFilter() end ix.option.Set("chatTabs", util.TableToJSON(tabs)) end function PLUGIN:SavePosition() local x, y = self.panel:GetPos() local width, height = self.panel:GetSize() ix.option.Set("chatPosition", util.TableToJSON({x, y, width, height})) end function PLUGIN:InitPostEntity() self:CreateChat() end function PLUGIN:PlayerBindPress(client, bind, pressed) bind = bind:lower() if (bind:find("messagemode") and pressed) then self.panel:SetActive(true) return true end end function PLUGIN:OnPauseMenuShow() if (!IsValid(ix.gui.chat) or !ix.gui.chat:GetActive()) then return end ix.gui.chat:SetActive(false) return false end function PLUGIN:HUDShouldDraw(element) if (element == "CHudChat") then return false end end function PLUGIN:ScreenResolutionChanged(oldWidth, oldHeight) self:CreateChat() end function PLUGIN:ChatText(index, name, text, messageType) if (messageType == "none" and IsValid(self.panel)) then self.panel:AddMessage(text) end end -- luacheck: globals chat chat.ixAddText = chat.ixAddText or chat.AddText function chat.AddText(...) if (IsValid(PLUGIN.panel)) then PLUGIN.panel:AddMessage(...) end -- log chat message to console local text = {} for _, v in ipairs({...}) do if (istable(v) or isstring(v)) then text[#text + 1] = v elseif (isentity(v) and v:IsPlayer()) then text[#text + 1] = team.GetColor(v:Team()) text[#text + 1] = v:Name() elseif (type(v) != "IMaterial") then text[#text + 1] = tostring(v) end end text[#text + 1] = "\n" MsgC(unpack(text)) end else util.AddNetworkString("ixChatMessage") net.Receive("ixChatMessage", function(length, client) local text = net.ReadString() if ((client.ixNextChat or 0) < CurTime() and isstring(text) and text:find("%S")) then local maxLength = ix.config.Get("chatMax") if (text:utf8len() > maxLength) then text = text:utf8sub(0, maxLength) end hook.Run("PlayerSay", client, text) client.ixNextChat = CurTime() + 0.5 end end) end ================================================ FILE: plugins/containers/entities/entities/ix_container.lua ================================================ ENT.Type = "anim" ENT.PrintName = "Container" ENT.Category = "Helix" ENT.Spawnable = false ENT.bNoPersist = true function ENT:SetupDataTables() self:NetworkVar("Int", 0, "ID") self:NetworkVar("Bool", 0, "Locked") self:NetworkVar("String", 0, "DisplayName") end if (SERVER) then function ENT:Initialize() self:PhysicsInit(SOLID_VPHYSICS) self:SetSolid(SOLID_VPHYSICS) self:SetUseType(SIMPLE_USE) self.receivers = {} local definition = ix.container.stored[self:GetModel():lower()] if (definition) then self:SetDisplayName(definition.name) end local physObj = self:GetPhysicsObject() if (IsValid(physObj)) then physObj:EnableMotion(true) physObj:Wake() end end function ENT:SetInventory(inventory) if (inventory) then self:SetID(inventory:GetID()) end end function ENT:SetMoney(amount) self.money = math.max(0, math.Round(tonumber(amount) or 0)) end function ENT:GetMoney() return self.money or 0 end function ENT:OnRemove() local index = self:GetID() if (!ix.shuttingDown and !self.ixIsSafe and ix.entityDataLoaded and index) then local inventory = ix.item.inventories[index] if (inventory) then ix.item.inventories[index] = nil local query = mysql:Delete("ix_items") query:Where("inventory_id", index) query:Execute() query = mysql:Delete("ix_inventories") query:Where("inventory_id", index) query:Execute() hook.Run("ContainerRemoved", self, inventory) end end end function ENT:OpenInventory(activator) local inventory = self:GetInventory() if (inventory) then local name = self:GetDisplayName() local definition = ix.container.stored[self:GetModel():lower()] ix.storage.Open(activator, inventory, { name = name, entity = self, searchTime = ix.config.Get("containerOpenTime", 0.7), data = {money = self:GetMoney()}, OnPlayerOpen = function() if (definition.OnOpen) then definition.OnOpen(self, activator) end end, OnPlayerClose = function() if (definition.OnClose) then definition.OnClose(self, activator) end ix.log.Add(activator, "closeContainer", name, inventory:GetID()) end }) if (self:GetLocked()) then self.Sessions[activator:GetCharacter():GetID()] = true end ix.log.Add(activator, "openContainer", name, inventory:GetID()) end end function ENT:Use(activator) local inventory = self:GetInventory() if (inventory and (activator.ixNextOpen or 0) < CurTime()) then local character = activator:GetCharacter() if (character) then local definition = ix.container.stored[self:GetModel():lower()] if (self:GetLocked() and !self.Sessions[character:GetID()]) then self:EmitSound(definition.locksound or "doors/default_locked.wav") if (!self.keypad) then net.Start("ixContainerPassword") net.WriteEntity(self) net.Send(activator) end else self:OpenInventory(activator) end end activator.ixNextOpen = CurTime() + 1 end end else ENT.PopulateEntityInfo = true local COLOR_LOCKED = Color(200, 38, 19, 200) local COLOR_UNLOCKED = Color(135, 211, 124, 200) function ENT:OnPopulateEntityInfo(tooltip) local definition = ix.container.stored[self:GetModel():lower()] local bLocked = self:GetLocked() surface.SetFont("ixIconsSmall") local iconText = bLocked and "P" or "Q" local iconWidth, iconHeight = surface.GetTextSize(iconText) -- minimal tooltips have centered text so we'll draw the icon above the name instead if (tooltip:IsMinimal()) then local icon = tooltip:AddRow("icon") icon:SetFont("ixIconsSmall") icon:SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED) icon:SetText(iconText) icon:SizeToContents() end local title = tooltip:AddRow("name") title:SetImportant() title:SetText(self:GetDisplayName()) title:SetBackgroundColor(ix.config.Get("color")) title:SetTextInset(iconWidth + 8, 0) title:SizeToContents() if (!tooltip:IsMinimal()) then title.Paint = function(panel, width, height) panel:PaintBackground(width, height) surface.SetFont("ixIconsSmall") surface.SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED) surface.SetTextPos(4, height * 0.5 - iconHeight * 0.5) surface.DrawText(iconText) end end local description = tooltip:AddRow("description") description:SetText(definition.description) description:SizeToContents() end end function ENT:GetInventory() return ix.item.inventories[self:GetID()] end ================================================ FILE: plugins/containers/sh_definitions.lua ================================================ --[[ ix.container.Register(model, { name = "Crate", description = "A simple wooden create.", width = 4, height = 4, locksound = "", opensound = "" }) ]]-- ix.container.Register("models/props_junk/wood_crate001a.mdl", { name = "Crate", description = "A simple wooden crate.", width = 4, height = 4, }) ix.container.Register("models/props_c17/lockers001a.mdl", { name = "Locker", description = "A white locker.", width = 3, height = 5, }) ix.container.Register("models/props_wasteland/controlroom_storagecloset001a.mdl", { name = "Metal Cabinet", description = "A green metal cabinet.", width = 4, height = 5, }) ix.container.Register("models/props_wasteland/controlroom_storagecloset001b.mdl", { name = "Metal Cabinet", description = "A green metal cabinet.", width = 4, height = 5, }) ix.container.Register("models/props_wasteland/controlroom_filecabinet001a.mdl", { name = "File Cabinet", description = "A metal filing cabinet.", width = 5, height = 3 }) ix.container.Register("models/props_wasteland/controlroom_filecabinet002a.mdl", { name = "File Cabinet", description = "A metal filing cabinet.", width = 3, height = 6, }) ix.container.Register("models/props_lab/filecabinet02.mdl", { name = "File Cabinet", description = "A metal filing cabinet.", width = 5, height = 3 }) ix.container.Register("models/props_c17/furniturefridge001a.mdl", { name = "Refrigerator", description = "A metal box for keeping food in.", width = 2, height = 3, }) ix.container.Register("models/props_wasteland/kitchen_fridge001a.mdl", { name = "Large Refrigerator", description = "A large metal box for storing even more food in.", width = 4, height = 5, }) ix.container.Register("models/props_junk/trashbin01a.mdl", { name = "Trash Bin", description = "What do you expect to find in here?", width = 2, height = 2, }) ix.container.Register("models/props_junk/trashdumpster01a.mdl", { name = "Dumpster", description = "A dumpster meant to stow away trash. It emanates an unpleasant smell.", width = 6, height = 3 }) ix.container.Register("models/items/ammocrate_smg1.mdl", { name = "Ammo Crate", description = "A heavy crate that stores ammo.", width = 5, height = 3, OnOpen = function(entity, activator) local closeSeq = entity:LookupSequence("Close") entity:ResetSequence(closeSeq) timer.Simple(2, function() if (entity and IsValid(entity)) then local openSeq = entity:LookupSequence("Open") entity:ResetSequence(openSeq) end end) end }) ix.container.Register("models/props_forest/footlocker01_closed.mdl", { name = "Footlocker", description = "A small chest to store belongings in.", width = 5, height = 3 }) ix.container.Register("models/Items/item_item_crate.mdl", { name = "Item Crate", description = "A crate to store some belongings in.", width = 5, height = 3 }) ix.container.Register("models/props_c17/cashregister01a.mdl", { name = "Cash Register", description = "A register with some buttons and a drawer.", width = 2, height = 1 }) ================================================ FILE: plugins/containers/sh_plugin.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Containers" PLUGIN.author = "Chessnut" PLUGIN.description = "Provides the ability to store items." ix.container = ix.container or {} ix.container.stored = ix.container.stored or {} ix.config.Add("containerSave", true, "Whether or not containers will save after a server restart.", nil, { category = "Containers" }) ix.config.Add("containerOpenTime", 0.7, "How long it takes to open a container.", nil, { data = {min = 0, max = 50}, category = "Containers" }) function ix.container.Register(model, data) ix.container.stored[model:lower()] = data end ix.util.Include("sh_definitions.lua") if (SERVER) then util.AddNetworkString("ixContainerPassword") function PLUGIN:PlayerSpawnedProp(client, model, entity) model = tostring(model):lower() local data = ix.container.stored[model] if (data) then if (hook.Run("CanPlayerSpawnContainer", client, model, entity) == false) then return end local container = ents.Create("ix_container") container:SetPos(entity:GetPos()) container:SetAngles(entity:GetAngles()) container:SetModel(model) container:Spawn() ix.inventory.New(0, "container:" .. model:lower(), function(inventory) -- we'll technically call this a bag since we don't want other bags to go inside inventory.vars.isBag = true inventory.vars.isContainer = true if (IsValid(container)) then container:SetInventory(inventory) self:SaveContainer() end end) entity:Remove() end end function PLUGIN:CanSaveContainer(entity, inventory) return ix.config.Get("containerSave", true) end function PLUGIN:SaveContainer() local data = {} for _, v in ipairs(ents.FindByClass("ix_container")) do if (hook.Run("CanSaveContainer", v, v:GetInventory()) != false) then local inventory = v:GetInventory() if (inventory) then data[#data + 1] = { v:GetPos(), v:GetAngles(), inventory:GetID(), v:GetModel(), v.password, v:GetDisplayName(), v:GetMoney() } end else local index = v:GetID() local query = mysql:Delete("ix_items") query:Where("inventory_id", index) query:Execute() query = mysql:Delete("ix_inventories") query:Where("inventory_id", index) query:Execute() end end self:SetData(data) end function PLUGIN:SaveData() if (!ix.shuttingDown) then self:SaveContainer() end end function PLUGIN:ContainerRemoved(entity, inventory) self:SaveContainer() end function PLUGIN:LoadData() local data = self:GetData() if (data) then for _, v in ipairs(data) do local data2 = ix.container.stored[v[4]:lower()] if (data2) then local inventoryID = tonumber(v[3]) if (!inventoryID or inventoryID < 1) then ErrorNoHalt(string.format( "[Helix] Attempted to restore container inventory with invalid inventory ID '%s' (%s, %s)\n", tostring(inventoryID), v[6] or "no name", v[4] or "no model")) continue end local entity = ents.Create("ix_container") entity:SetPos(v[1]) entity:SetAngles(v[2]) entity:Spawn() entity:SetModel(v[4]) entity:SetSolid(SOLID_VPHYSICS) entity:PhysicsInit(SOLID_VPHYSICS) if (v[5]) then entity.password = v[5] entity:SetLocked(true) entity.Sessions = {} entity.PasswordAttempts = {} end if (v[6]) then entity:SetDisplayName(v[6]) end if (v[7]) then entity:SetMoney(v[7]) end ix.inventory.Restore(inventoryID, data2.width, data2.height, function(inventory) inventory.vars.isBag = true inventory.vars.isContainer = true if (IsValid(entity)) then entity:SetInventory(inventory) end end) local physObject = entity:GetPhysicsObject() if (IsValid(physObject)) then physObject:EnableMotion() end end end end end net.Receive("ixContainerPassword", function(length, client) if ((client.ixNextContainerPassword or 0) > RealTime()) then return end local entity = net.ReadEntity() local steamID = client:SteamID() local attempts = entity.PasswordAttempts[steamID] if (attempts and attempts >= 10) then client:NotifyLocalized("passwordAttemptLimit") return end local password = net.ReadString() local dist = entity:GetPos():DistToSqr(client:GetPos()) if (dist < 16384 and password) then if (entity.password and entity.password == password) then entity:OpenInventory(client) else entity.PasswordAttempts[steamID] = attempts and attempts + 1 or 1 client:NotifyLocalized("wrongPassword") end end client.ixNextContainerPassword = RealTime() + 1 end) ix.log.AddType("containerPassword", function(client, ...) local arg = {...} return string.format("%s has %s the password for '%s'.", client:Name(), arg[3] and "set" or "removed", arg[1], arg[2]) end) ix.log.AddType("containerName", function(client, ...) local arg = {...} if (arg[3]) then return string.format("%s has set container %d name to '%s'.", client:Name(), arg[2], arg[1]) else return string.format("%s has removed container %d name.", client:Name(), arg[2]) end end) ix.log.AddType("openContainer", function(client, ...) local arg = {...} return string.format("%s opened the '%s' #%d container.", client:Name(), arg[1], arg[2]) end, FLAG_NORMAL) ix.log.AddType("closeContainer", function(client, ...) local arg = {...} return string.format("%s closed the '%s' #%d container.", client:Name(), arg[1], arg[2]) end, FLAG_NORMAL) else net.Receive("ixContainerPassword", function(length) local entity = net.ReadEntity() Derma_StringRequest( L("containerPasswordWrite"), L("containerPasswordWrite"), "", function(val) net.Start("ixContainerPassword") net.WriteEntity(entity) net.WriteString(val) net.SendToServer() end ) end) end function PLUGIN:InitializedPlugins() for k, v in pairs(ix.container.stored) do if (v.name and v.width and v.height) then ix.inventory.Register("container:" .. k:lower(), v.width, v.height) else ErrorNoHalt("[Helix] Container for '"..k.."' is missing all inventory information!\n") ix.container.stored[k] = nil end end end -- properties properties.Add("container_setpassword", { MenuLabel = "Set Password", Order = 400, MenuIcon = "icon16/lock_edit.png", Filter = function(self, entity, client) if (entity:GetClass() != "ix_container") then return false end if (!gamemode.Call("CanProperty", client, "container_setpassword", entity)) then return false end return true end, Action = function(self, entity) Derma_StringRequest(L("containerPasswordWrite"), "", "", function(text) self:MsgStart() net.WriteEntity(entity) net.WriteString(text) self:MsgEnd() end) end, Receive = function(self, length, client) local entity = net.ReadEntity() if (!IsValid(entity)) then return end if (!self:Filter(entity, client)) then return end local password = net.ReadString() entity.Sessions = {} entity.PasswordAttempts = {} if (password:len() != 0) then entity:SetLocked(true) entity.password = password client:NotifyLocalized("containerPassword", password) else entity:SetLocked(false) entity.password = nil client:NotifyLocalized("containerPasswordRemove") end local name = entity:GetDisplayName() local inventory = entity:GetInventory() ix.log.Add(client, "containerPassword", name, inventory:GetID(), password:len() != 0) end }) properties.Add("container_setname", { MenuLabel = "Set Name", Order = 400, MenuIcon = "icon16/tag_blue_edit.png", Filter = function(self, entity, client) if (entity:GetClass() != "ix_container") then return false end if (!gamemode.Call("CanProperty", client, "container_setname", entity)) then return false end return true end, Action = function(self, entity) Derma_StringRequest(L("containerNameWrite"), "", "", function(text) self:MsgStart() net.WriteEntity(entity) net.WriteString(text) self:MsgEnd() end) end, Receive = function(self, length, client) local entity = net.ReadEntity() if (!IsValid(entity)) then return end if (!self:Filter(entity, client)) then return end local name = net.ReadString() if (name:len() != 0) then entity:SetDisplayName(name) client:NotifyLocalized("containerName", name) else local definition = ix.container.stored[entity:GetModel():lower()] entity:SetDisplayName(definition.name) client:NotifyLocalized("containerNameRemove") end local inventory = entity:GetInventory() ix.log.Add(client, "containerName", name, inventory:GetID(), name:len() != 0) end }) ================================================ FILE: plugins/crosshair.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Crosshair" PLUGIN.author = "Black Tea" PLUGIN.description = "A Crosshair." if (CLIENT) then local function drawdot( pos, size, col ) local color = col[2] surface.SetDrawColor(color.r, color.g, color.b, color.a) surface.DrawRect(pos[1] - size/2, pos[2] - size/2, size, size) color = col[1] surface.SetDrawColor(color.r, color.g, color.b, color.a) surface.DrawOutlinedRect(pos[1] - size/2, pos[2] - size/2 , size, size) end local aimVector, punchAngle, ft, screen, scaleFraction, distance local math_round = math.Round local curGap = 0 local curAlpha = 0 local maxDistance = 1000 ^ 2 local crossSize = 4 local crossGap = 0 local colors = {color_black} local filter = {} function PLUGIN:DrawCrosshair(x, y, trace) local entity = trace.Entity distance = trace.StartPos:DistToSqr(trace.HitPos) scaleFraction = 1 - math.Clamp(distance / maxDistance, 0, .5) crossSize = 4 crossGap = 25 * (scaleFraction - (LocalPlayer():IsWepRaised() and 0 or .1)) if (IsValid(entity) and entity:GetClass() == "ix_item" and entity:GetPos():DistToSqr(trace.StartPos) <= 16384) then crossGap = 0 crossSize = 5 end curGap = Lerp(ft * 2, curGap, crossGap) curAlpha = Lerp(ft * 2, curAlpha, !LocalPlayer():IsWepRaised() and 255 or 150) curAlpha = hook.Run("GetCrosshairAlpha", curAlpha) or curAlpha colors[2] = Color(255, curAlpha, curAlpha, curAlpha) if (curAlpha > 1) then drawdot( {math_round(screen.x), math_round(screen.y)}, crossSize, colors) drawdot( {math_round(screen.x + curGap), math_round(screen.y)}, crossSize, colors) drawdot( {math_round(screen.x - curGap), math_round(screen.y)}, crossSize, colors) drawdot( {math_round(screen.x), math_round(screen.y + curGap * .8)}, crossSize, colors) drawdot( {math_round(screen.x), math_round(screen.y - curGap * .8)}, crossSize, colors) end end -- luacheck: globals g_ContextMenu function PLUGIN:PostDrawHUD() local client = LocalPlayer() if (!client:GetCharacter() or !client:Alive()) then return end local entity = Entity(client:GetLocalVar("ragdoll", 0)) if (entity:IsValid()) then return end local wep = client:GetActiveWeapon() local bShouldDraw = hook.Run("ShouldDrawCrosshair", client, wep) if (bShouldDraw == false or !IsValid(wep) or wep.DrawCrosshair == false) then return end if (bShouldDraw == false or g_ContextMenu:IsVisible() or (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing())) then return end aimVector = client:EyeAngles() punchAngle = client:GetViewPunchAngles() ft = FrameTime() filter = {client} local vehicle = client:GetVehicle() if (vehicle and IsValid(vehicle)) then aimVector = aimVector + vehicle:GetAngles() table.insert(filter, vehicle) end local data = {} data.start = client:GetShootPos() data.endpos = data.start + (aimVector + punchAngle):Forward() * 65535 data.filter = filter local trace = util.TraceLine(data) local drawTarget = self local drawFunction = self.DrawCrosshair -- we'll manually call this since CHudCrosshair is never drawn; checks are already performed if (wep.DoDrawCrosshair) then drawTarget = wep drawFunction = wep.DoDrawCrosshair end screen = trace.HitPos:ToScreen() drawFunction(drawTarget, screen.x, screen.y, trace) end end ================================================ FILE: plugins/doors/cl_plugin.lua ================================================ -- luacheck: globals ACCESS_LABELS ACCESS_LABELS = {} ACCESS_LABELS[DOOR_OWNER] = "owner" ACCESS_LABELS[DOOR_TENANT] = "tenant" ACCESS_LABELS[DOOR_GUEST] = "guest" ACCESS_LABELS[DOOR_NONE] = "none" function PLUGIN:GetDefaultDoorInfo(door) local owner = IsValid(door:GetDTEntity(0)) and door:GetDTEntity(0) or nil local name = door:GetNetVar("title", door:GetNetVar("name", IsValid(owner) and L"dTitleOwned" or L"dTitle")) local description = door:GetNetVar("ownable") and L("dIsOwnable") or L("dIsNotOwnable") local color = ix.config.Get("color") local faction = door:GetNetVar("faction") local class = door:GetNetVar("class") if (class) then local classData = ix.class.list[class] if (classData) then if (classData.color) then color = classData.color end if (!owner) then description = L("dOwnedBy", L2(classData.name) or classData.name) end end elseif (faction) then local info = ix.faction.indices[faction] color = team.GetColor(faction) if (info and !owner) then description = L("dOwnedBy", L2(info.name) or info.name) end end if (owner) then description = L("dOwnedBy", owner:GetName()) end return { name = name, description = description, color = color } end function PLUGIN:DrawDoorInfo(door, width, position, angles, scale, clientPosition) local alpha = math.max((1 - clientPosition:DistToSqr(door:GetPos()) / 65536) * 255, 0) if (alpha < 1) then return end local info = hook.Run("GetDoorInfo", door) or self:GetDefaultDoorInfo(door) if (!istable(info) or table.IsEmpty(info)) then return end -- title + background surface.SetFont("ix3D2DMediumFont") local nameWidth, nameHeight = surface.GetTextSize(info.name) derma.SkinFunc("DrawImportantBackground", -width * 0.5, -nameHeight * 0.5, width, nameHeight, ColorAlpha(info.color, alpha * 0.5)) surface.SetTextColor(ColorAlpha(color_white, alpha)) surface.SetTextPos(-nameWidth * 0.5, -nameHeight * 0.5) surface.DrawText(info.name) -- description local lines = ix.util.WrapText(info.description, width, "ix3D2DSmallFont") local y = nameHeight * 0.5 + 4 for i = 1, #lines do local line = lines[i] local textWidth, textHeight = surface.GetTextSize(line) surface.SetTextPos(-textWidth * 0.5, y) surface.DrawText(line) y = y + textHeight end -- background blur ix.util.PushBlur(function() cam.Start3D2D(position, angles, scale) surface.SetDrawColor(11, 11, 11, math.max(alpha - 100, 0)) surface.DrawRect(-width * 0.5, -nameHeight * 0.5, width, y + nameHeight * 0.5 + 4) cam.End3D2D() end) end function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox) if (bDepth or bSkybox or !LocalPlayer():GetCharacter()) then return end local entities = ents.FindInSphere(EyePos(), 256) local clientPosition = LocalPlayer():GetPos() for _, v in ipairs(entities) do if (!IsValid(v) or !v:IsDoor() or !v:GetNetVar("visible")) then continue end local color = v:GetColor() if (v:IsEffectActive(EF_NODRAW) or color.a <= 0) then continue end local position = v:LocalToWorld(v:OBBCenter()) local mins, maxs = v:GetCollisionBounds() local width = 0 local size = maxs - mins local trace = { collisiongroup = COLLISION_GROUP_WORLD, ignoreworld = true, endpos = position } -- trace from shortest side to center to get correct position for rendering if (size.z < size.x and size.z < size.y) then trace.start = position - v:GetUp() * size.z width = size.y elseif (size.x < size.y) then trace.start = position - v:GetForward() * size.x width = size.y elseif (size.y < size.x) then trace.start = position - v:GetRight() * size.y width = size.x end width = math.max(width, 12) trace = util.TraceLine(trace) local angles = trace.HitNormal:Angle() local anglesOpposite = trace.HitNormal:Angle() angles:RotateAroundAxis(angles:Forward(), 90) angles:RotateAroundAxis(angles:Right(), 90) anglesOpposite:RotateAroundAxis(anglesOpposite:Forward(), 90) anglesOpposite:RotateAroundAxis(anglesOpposite:Right(), -90) local positionFront = trace.HitPos - (((position - trace.HitPos):Length() * 2) + 1) * trace.HitNormal local positionOpposite = trace.HitPos + (trace.HitNormal * 2) if (trace.HitNormal:Dot((clientPosition - position):GetNormalized()) < 0) then -- draw front cam.Start3D2D(positionFront, angles, 0.1) self:DrawDoorInfo(v, width * 8, positionFront, angles, 0.1, clientPosition) cam.End3D2D() else -- draw back cam.Start3D2D(positionOpposite, anglesOpposite, 0.1) self:DrawDoorInfo(v, width * 8, positionOpposite, anglesOpposite, 0.1, clientPosition) cam.End3D2D() end end end net.Receive("ixDoorMenu", function() if (IsValid(ix.gui.door)) then return ix.gui.door:Remove() end local door = net.ReadEntity() local access = net.ReadTable() if (IsValid(door) and !table.IsEmpty(access)) then local entity = net.ReadEntity() ix.gui.door = vgui.Create("ixDoorMenu") ix.gui.door:SetDoor(door, access, entity) end end) net.Receive("ixDoorPermission", function() local door = net.ReadEntity() if (!IsValid(door)) then return end local target = net.ReadEntity() local access = net.ReadUInt(4) local panel = door.ixPanel if (IsValid(panel) and IsValid(target)) then panel.access[target] = access for _, v in ipairs(panel.access:GetLines()) do if (v.player == target) then v:SetColumnText(2, L(ACCESS_LABELS[access or 0])) return end end end end) ================================================ FILE: plugins/doors/derma/cl_door.lua ================================================ local PANEL = {} local function DoorSetPermission(door, target, permission) net.Start("ixDoorPermission") net.WriteEntity(door) net.WriteEntity(target) net.WriteUInt(permission, 4) net.SendToServer() end function PANEL:Init() self:SetSize(280, 240) self:SetTitle(L"doorSettings") self:Center() self:MakePopup() self.access = self:Add("DListView") self.access:Dock(FILL) self.access:AddColumn(L"name").Header:SetTextColor(Color(25, 25, 25)) self.access:AddColumn(L"access").Header:SetTextColor(Color(25, 25, 25)) self.access.OnClickLine = function(this, line, selected) if (IsValid(line.player)) then local menu = DermaMenu() menu:AddOption(L"tenant", function() if (self.accessData and self.accessData[line.player] != DOOR_TENANT) then DoorSetPermission(self.door, line.player, DOOR_TENANT) end end):SetImage("icon16/user_add.png") menu:AddOption(L"guest", function() if (self.accessData and self.accessData[line.player] != DOOR_GUEST) then DoorSetPermission(self.door, line.player, DOOR_GUEST) end end):SetImage("icon16/user_green.png") menu:AddOption(L"none", function() if (self.accessData and self.accessData[line.player] != DOOR_NONE) then DoorSetPermission(self.door, line.player, DOOR_NONE) end end):SetImage("icon16/user_red.png") menu:Open() end end end function PANEL:SetDoor(door, access, door2) door.ixPanel = self self.accessData = access self.door = door for _, v in player.Iterator() do if (v != LocalPlayer() and v:GetCharacter()) then self.access:AddLine(v:Name(), L(ACCESS_LABELS[access[v] or 0])).player = v end end if (self:CheckAccess(DOOR_OWNER)) then self.sell = self:Add("DButton") self.sell:Dock(BOTTOM) self.sell:SetText(L"sell") self.sell:SetTextColor(color_white) self.sell:DockMargin(0, 5, 0, 0) self.sell.DoClick = function(this) self:Remove() ix.command.Send("doorsell") end end if (self:CheckAccess(DOOR_TENANT)) then self.name = self:Add("DTextEntry") self.name:Dock(TOP) self.name:DockMargin(0, 0, 0, 5) self.name.Think = function(this) if (!this:IsEditing()) then local entity = IsValid(door2) and door2 or door self.name:SetText(entity:GetNetVar("title", L"dTitleOwned")) end end self.name.OnEnter = function(this) ix.command.Send("doorsettitle", this:GetText()) end end end function PANEL:CheckAccess(access) access = access or DOOR_GUEST if ((self.accessData[LocalPlayer()] or 0) >= access) then return true end return false end function PANEL:Think() if (self.accessData and !IsValid(self.door) and self:CheckAccess()) then self:Remove() end end vgui.Register("ixDoorMenu", PANEL, "DFrame") ================================================ FILE: plugins/doors/entities/weapons/ix_keys.lua ================================================ AddCSLuaFile() if (CLIENT) then SWEP.PrintName = "Keys" SWEP.Slot = 0 SWEP.SlotPos = 2 SWEP.DrawAmmo = false SWEP.DrawCrosshair = false end SWEP.Author = "Chessnut" SWEP.Instructions = "Primary Fire: Lock\nSecondary Fire: Unlock" SWEP.Purpose = "Hitting things and knocking on doors." SWEP.Drop = false SWEP.ViewModelFOV = 45 SWEP.ViewModelFlip = false SWEP.AnimPrefix = "rpg" SWEP.ViewTranslation = 4 SWEP.Primary.ClipSize = -1 SWEP.Primary.DefaultClip = -1 SWEP.Primary.Automatic = false SWEP.Primary.Ammo = "" SWEP.Primary.Damage = 5 SWEP.Primary.Delay = 0.75 SWEP.Secondary.ClipSize = -1 SWEP.Secondary.DefaultClip = 0 SWEP.Secondary.Automatic = false SWEP.Secondary.Ammo = "" SWEP.ViewModel = Model("models/weapons/c_arms_animations.mdl") SWEP.WorldModel = "" SWEP.UseHands = false SWEP.LowerAngles = Angle(0, 5, -14) SWEP.LowerAngles2 = Angle(0, 5, -22) SWEP.IsAlwaysLowered = true SWEP.FireWhenLowered = true SWEP.HoldType = "passive" -- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER ACT_VM_FISTS_DRAW = 2 ACT_VM_FISTS_HOLSTER = 1 function SWEP:Holster() if (!IsValid(self.Owner)) then return end local viewModel = self.Owner:GetViewModel() if (IsValid(viewModel)) then viewModel:SetPlaybackRate(1) viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER) end return true end function SWEP:Precache() end function SWEP:Initialize() self:SetHoldType(self.HoldType) end function SWEP:PrimaryAttack() local time = ix.config.Get("doorLockTime", 1) local time2 = math.max(time, 1) self:SetNextPrimaryFire(CurTime() + time2) self:SetNextSecondaryFire(CurTime() + time2) if (!IsFirstTimePredicted()) then return end if (CLIENT) then return end local data = {} data.start = self.Owner:GetShootPos() data.endpos = data.start + self.Owner:GetAimVector()*96 data.filter = self.Owner local entity = util.TraceLine(data).Entity --[[ Locks the entity if the contiditon fits: 1. The entity is door and client has access to the door. 2. The entity is vehicle and the "owner" variable is same as client's character ID. --]] if (IsValid(entity) and ( (entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or (entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner) ) ) then self.Owner:SetAction("@locking", time, function() self:ToggleLock(entity, true) end) return end end function SWEP:ToggleLock(door, state) if (IsValid(self.Owner) and self.Owner:GetPos():Distance(door:GetPos()) > 96) then return end if (door:IsDoor()) then local partner = door:GetDoorPartner() if (state) then if (IsValid(partner)) then partner:Fire("lock") end door:Fire("lock") self.Owner:EmitSound("doors/door_latch3.wav") hook.Run("PlayerLockedDoor", self.Owner, door, partner) else if (IsValid(partner)) then partner:Fire("unlock") end door:Fire("unlock") self.Owner:EmitSound("doors/door_latch1.wav") hook.Run("PlayerUnlockedDoor", self.Owner, door, partner) end elseif (door:IsVehicle()) then if (state) then door:Fire("lock") if (door.IsSimfphyscar) then door.IsLocked = true end self.Owner:EmitSound("doors/door_latch3.wav") hook.Run("PlayerLockedVehicle", self.Owner, door) else door:Fire("unlock") if (door.IsSimfphyscar) then door.IsLocked = nil end self.Owner:EmitSound("doors/door_latch1.wav") hook.Run("PlayerUnlockedVehicle", self.Owner, door) end end end function SWEP:SecondaryAttack() local time = ix.config.Get("doorLockTime", 1) local time2 = math.max(time, 1) self:SetNextPrimaryFire(CurTime() + time2) self:SetNextSecondaryFire(CurTime() + time2) if (!IsFirstTimePredicted()) then return end if (CLIENT) then return end local data = {} data.start = self.Owner:GetShootPos() data.endpos = data.start + self.Owner:GetAimVector()*96 data.filter = self.Owner local entity = util.TraceLine(data).Entity --[[ Unlocks the entity if the contiditon fits: 1. The entity is door and client has access to the door. 2. The entity is vehicle and the "owner" variable is same as client's character ID. ]]-- if (IsValid(entity) and ( (entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or (entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner) ) ) then self.Owner:SetAction("@unlocking", time, function() self:ToggleLock(entity, false) end) return end end ================================================ FILE: plugins/doors/sh_commands.lua ================================================ local PLUGIN = PLUGIN ix.command.Add("DoorSell", { description = "@cmdDoorSell", OnRun = function(self, client, arguments) -- Get the entity 96 units infront of the player. local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local trace = util.TraceLine(data) local entity = trace.Entity -- Check if the entity is a valid door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then -- Check if the player owners the door. if (client == entity:GetDTEntity(0)) then entity = IsValid(entity.ixParent) and entity.ixParent or entity -- Get the price that the door is sold for. local price = math.Round(entity:GetNetVar("price", ix.config.Get("doorCost")) * ix.config.Get("doorSellRatio")) local character = client:GetCharacter() -- Remove old door information. entity:RemoveDoorAccessData() local doors = character:GetVar("doors") or {} for k, v in ipairs(doors) do if (v == entity) then table.remove(doors, k) end end character:SetVar("doors", doors, true) -- Take their money and notify them. character:GiveMoney(price) hook.Run("OnPlayerPurchaseDoor", client, entity, false, PLUGIN.CallOnDoorChildren) ix.log.Add(client, "selldoor") return "@dSold", ix.currency.Get(price) else -- Otherwise tell them they can not. return "@notOwner" end else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorBuy", { description = "@cmdDoorBuy", OnRun = function(self, client, arguments) -- Get the entity 96 units infront of the player. local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local trace = util.TraceLine(data) local entity = trace.Entity -- Check if the entity is a valid door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then if (!entity:GetNetVar("ownable") or entity:GetNetVar("faction") or entity:GetNetVar("class")) then return "@dNotAllowedToOwn" end if (IsValid(entity:GetDTEntity(0))) then return "@dOwnedBy", entity:GetDTEntity(0):Name() end entity = IsValid(entity.ixParent) and entity.ixParent or entity -- Get the price that the door is bought for. local price = entity:GetNetVar("price", ix.config.Get("doorCost")) local character = client:GetCharacter() -- Check if the player can actually afford it. if (character:HasMoney(price)) then -- Set the door to be owned by this player. entity:SetDTEntity(0, client) entity.ixAccess = { [client] = DOOR_OWNER } PLUGIN:CallOnDoorChildren(entity, function(child) child:SetDTEntity(0, client) end) local doors = character:GetVar("doors") or {} doors[#doors + 1] = entity character:SetVar("doors", doors, true) -- Take their money and notify them. character:TakeMoney(price) hook.Run("OnPlayerPurchaseDoor", client, entity, true, PLUGIN.CallOnDoorChildren) ix.log.Add(client, "buydoor") return "@dPurchased", ix.currency.Get(price) else -- Otherwise tell them they can not. return "@canNotAfford" end else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetUnownable", { description = "@cmdDoorSetUnownable", privilege = "Manage Doors", adminOnly = true, arguments = ix.type.text, OnRun = function(self, client, name) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then -- Set it so it is unownable. entity:SetNetVar("ownable", nil) -- Change the name of the door if needed. if (name:find("%S")) then entity:SetNetVar("name", name) end PLUGIN:CallOnDoorChildren(entity, function(child) child:SetNetVar("ownable", nil) if (name:find("%S")) then child:SetNetVar("name", name) end end) -- Save the door information. PLUGIN:SaveDoorData() return "@dMadeUnownable" else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetOwnable", { description = "@cmdDoorSetOwnable", privilege = "Manage Doors", adminOnly = true, arguments = ix.type.text, OnRun = function(self, client, name) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then -- Set it so it is ownable. entity:SetNetVar("ownable", true) entity:SetNetVar("visible", true) -- Update the name. if (name:find("%S")) then entity:SetNetVar("name", name) end PLUGIN:CallOnDoorChildren(entity, function(child) child:SetNetVar("ownable", true) child:SetNetVar("visible", true) if (name:find("%S")) then child:SetNetVar("name", name) end end) -- Save the door information. PLUGIN:SaveDoorData() return "@dMadeOwnable" else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetFaction", { description = "@cmdDoorSetFaction", privilege = "Manage Doors", adminOnly = true, arguments = bit.bor(ix.type.text, ix.type.optional), OnRun = function(self, client, name) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then if (!name or name == "") then entity.ixFactionID = nil entity:SetNetVar("faction", nil) PLUGIN:CallOnDoorChildren(entity, function() entity.ixFactionID = nil entity:SetNetVar("faction", nil) end) PLUGIN:SaveDoorData() return "@dRemoveFaction" end local faction -- Loop through each faction, checking the uniqueID and name. for k, v in pairs(ix.faction.teams) do if (ix.util.StringMatches(k, name) or ix.util.StringMatches(L(v.name, client), name)) then -- This faction matches the provided string. faction = v -- Escape the loop. break end end -- Check if a faction was found. if (faction) then entity.ixFactionID = faction.uniqueID entity:SetNetVar("faction", faction.index) PLUGIN:CallOnDoorChildren(entity, function() entity.ixFactionID = faction.uniqueID entity:SetNetVar("faction", faction.index) end) PLUGIN:SaveDoorData() return "@dSetFaction", L(faction.name, client) -- The faction was not found. else return "@invalidFaction" end end end }) ix.command.Add("DoorSetDisabled", { description = "@cmdDoorSetDisabled", privilege = "Manage Doors", adminOnly = true, arguments = ix.type.bool, OnRun = function(self, client, bDisabled) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor()) then -- Set it so it is ownable. entity:SetNetVar("disabled", bDisabled) PLUGIN:CallOnDoorChildren(entity, function(child) child:SetNetVar("disabled", bDisabled) end) PLUGIN:SaveDoorData() -- Tell the player they have made the door (un)disabled. return "@dSet" .. (bDisabled and "" or "Not") .. "Disabled" else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetTitle", { description = "@cmdDoorSetTitle", arguments = ix.type.text, OnRun = function(self, client, name) -- Get the door infront of the player. local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local trace = util.TraceLine(data) local entity = trace.Entity -- Validate the door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then -- Make sure the name contains actual characters. if (!name:find("%S")) then return "@invalidArg", 1 end --[[ NOTE: Here, we are setting two different networked names. The title is a temporary name, while the other name is the default name for the door. The reason for this is so when the server closes while someone owns the door, it doesn't save THEIR title, which could lead to unwanted things. --]] name = name:utf8sub(1, 24) -- Check if they are allowed to change the door's name. if (entity:CheckDoorAccess(client, DOOR_TENANT)) then entity:SetNetVar("title", name) elseif (CAMI.PlayerHasAccess(client, "Helix - Manage Doors", nil)) then entity:SetNetVar("name", name) PLUGIN:CallOnDoorChildren(entity, function(child) child:SetNetVar("name", name) end) else -- Otherwise notify the player he/she can't. return "@notOwner" end else -- Notification of the door not being valid. return "@dNotValid" end end }) ix.command.Add("DoorSetParent", { description = "@cmdDoorSetParent", privilege = "Manage Doors", adminOnly = true, OnRun = function(self, client, arguments) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then client.ixDoorParent = entity return "@dSetParentDoor" else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetChild", { description = "@cmdDoorSetChild", privilege = "Manage Doors", adminOnly = true, OnRun = function(self, client, arguments) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then if (client.ixDoorParent == entity) then return "@dCanNotSetAsChild" end -- Check if the player has set a door as a parent. if (IsValid(client.ixDoorParent)) then -- Add the door to the parent's list of children. client.ixDoorParent.ixChildren = client.ixDoorParent.ixChildren or {} client.ixDoorParent.ixChildren[entity:MapCreationID()] = true -- Set the door's parent to the parent. entity.ixParent = client.ixDoorParent -- Save the door information. PLUGIN:SaveDoorData() PLUGIN:CopyParentDoor(entity) return "@dAddChildDoor" else -- Tell the player they do not have a door parent. return "@dNoParentDoor" end else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorRemoveChild", { description = "@cmdDoorRemoveChild", privilege = "Manage Doors", adminOnly = true, OnRun = function(self, client, arguments) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then if (client.ixDoorParent == entity) then PLUGIN:CallOnDoorChildren(entity, function(child) child.ixParent = nil end) entity.ixChildren = nil return "@dRemoveChildren" end -- Check if the player has set a door as a parent. if (IsValid(entity.ixParent) and entity.ixParent.ixChildren) then -- Remove the door from the list of children. entity.ixParent.ixChildren[entity:MapCreationID()] = nil -- Remove the variable for the parent. entity.ixParent = nil PLUGIN:SaveDoorData() return "@dRemoveChildDoor" end else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetHidden", { description = "@cmdDoorSetHidden", privilege = "Manage Doors", adminOnly = true, arguments = ix.type.bool, OnRun = function(self, client, bHidden) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor()) then entity:SetNetVar("visible", !bHidden) PLUGIN:CallOnDoorChildren(entity, function(child) child:SetNetVar("visible", !bHidden) end) PLUGIN:SaveDoorData() -- Tell the player they have made the door (un)hidden. return "@dSet" .. (bHidden and "" or "Not") .. "Hidden" else -- Tell the player the door isn't valid. return "@dNotValid" end end }) ix.command.Add("DoorSetClass", { description = "@cmdDoorSetClass", privilege = "Manage Doors", adminOnly = true, arguments = bit.bor(ix.type.text, ix.type.optional), OnRun = function(self, client, name) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then if (!name or name == "") then entity:SetNetVar("class", nil) PLUGIN:CallOnDoorChildren(entity, function() entity:SetNetVar("class", nil) end) PLUGIN:SaveDoorData() return "@dRemoveClass" end local class, classData for k, v in pairs(ix.class.list) do if (ix.util.StringMatches(v.name, name) or ix.util.StringMatches(L(v.name, client), name)) then class, classData = k, v break end end -- Check if a faction was found. if (class) then entity.ixClassID = class entity:SetNetVar("class", class) PLUGIN:CallOnDoorChildren(entity, function() entity.ixClassID = class entity:SetNetVar("class", class) end) PLUGIN:SaveDoorData() return "@dSetClass", L(classData.name, client) else return "@invalidClass" end end end }) ================================================ FILE: plugins/doors/sh_plugin.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Doors" PLUGIN.author = "Chessnut" PLUGIN.description = "A simple door system." -- luacheck: globals DOOR_OWNER DOOR_TENANT DOOR_GUEST DOOR_NONE DOOR_OWNER = 3 DOOR_TENANT = 2 DOOR_GUEST = 1 DOOR_NONE = 0 ix.util.Include("sv_plugin.lua") ix.util.Include("cl_plugin.lua") ix.util.Include("sh_commands.lua") do local entityMeta = FindMetaTable("Entity") function entityMeta:CheckDoorAccess(client, access) if (!self:IsDoor()) then return false end access = access or DOOR_GUEST local parent = self.ixParent if (IsValid(parent)) then return parent:CheckDoorAccess(client, access) end if (hook.Run("CanPlayerAccessDoor", client, self, access)) then return true end if (self.ixAccess and (self.ixAccess[client] or 0) >= access) then return true end return false end if (SERVER) then function entityMeta:RemoveDoorAccessData() local receivers = {} for k, _ in pairs(self.ixAccess or {}) do receivers[#receivers + 1] = k end if (#receivers > 0) then net.Start("ixDoorMenu") net.WriteEntity(self) net.WriteTable({}) net.Send(receivers) end self.ixAccess = {} self:SetDTEntity(0, nil) -- Remove door information on child doors PLUGIN:CallOnDoorChildren(self, function(child) child:SetDTEntity(0, nil) end) end end end -- Configurations for door prices. ix.config.Add("doorCost", 10, "The price to purchase a door.", nil, { data = {min = 0, max = 500}, category = "dConfigName" }) ix.config.Add("doorSellRatio", 0.5, "How much of the door price is returned when selling a door.", nil, { data = {min = 0, max = 1.0, decimals = 1}, category = "dConfigName" }) ix.config.Add("doorLockTime", 1, "How long it takes to (un)lock a door.", nil, { data = {min = 0, max = 10.0, decimals = 1}, category = "dConfigName" }) ================================================ FILE: plugins/doors/sv_plugin.lua ================================================ util.AddNetworkString("ixDoorMenu") util.AddNetworkString("ixDoorPermission") -- Variables for door data. local variables = { -- Whether or not the door will be disabled. "disabled", -- The name of the door. "name", -- Price of the door. "price", -- If the door is ownable. "ownable", -- The faction that owns a door. "faction", -- The class that owns a door. "class", -- Whether or not the door will be hidden. "visible" } function PLUGIN:CallOnDoorChildren(entity, callback) local parent if (entity.ixChildren) then parent = entity elseif (entity.ixParent) then parent = entity.ixParent end if (IsValid(parent)) then callback(parent) for k, _ in pairs(parent.ixChildren) do local child = ents.GetMapCreatedEntity(k) if (IsValid(child)) then callback(child) end end end end function PLUGIN:CopyParentDoor(child) local parent = child.ixParent if (IsValid(parent)) then for _, v in ipairs(variables) do local value = parent:GetNetVar(v) if (child:GetNetVar(v) != value) then child:SetNetVar(v, value) end end end end -- Called after the entities have loaded. function PLUGIN:LoadData() -- Restore the saved door information. local data = self:GetData() if (!data) then return end -- Loop through all of the saved doors. for k, v in pairs(data) do -- Get the door entity from the saved ID. local entity = ents.GetMapCreatedEntity(k) -- Check it is a valid door in-case something went wrong. if (IsValid(entity) and entity:IsDoor()) then -- Loop through all of our door variables. for k2, v2 in pairs(v) do if (k2 == "children") then entity.ixChildren = v2 for index, _ in pairs(v2) do local door = ents.GetMapCreatedEntity(index) if (IsValid(door)) then door.ixParent = entity end end elseif (k2 == "faction") then for k3, v3 in pairs(ix.faction.teams) do if (k3 == v2) then entity.ixFactionID = k3 entity:SetNetVar("faction", v3.index) break end end else entity:SetNetVar(k2, v2) end end end end end -- Called before the gamemode shuts down. function PLUGIN:SaveDoorData() -- Create an empty table to save information in. local data = {} local doors = {} for _, v in ents.Iterator() do if (v:IsDoor()) then doors[v:MapCreationID()] = v end end local doorData -- Loop through doors with information. for k, v in pairs(doors) do -- Another empty table for actual information regarding the door. doorData = {} -- Save all of the needed variables to the doorData table. for _, v2 in ipairs(variables) do local value = v:GetNetVar(v2) if (value) then doorData[v2] = v:GetNetVar(v2) end end if (v.ixChildren) then doorData.children = v.ixChildren end if (v.ixClassID) then doorData.class = v.ixClassID end if (v.ixFactionID) then doorData.faction = v.ixFactionID end -- Add the door to the door information. if (!table.IsEmpty(doorData)) then data[k] = doorData end end -- Save all of the door information. self:SetData(data) end function PLUGIN:CanPlayerUseDoor(client, entity) if (entity:GetNetVar("disabled")) then return false end end -- Whether or not a player a player has any abilities over the door, such as locking. function PLUGIN:CanPlayerAccessDoor(client, door, access) local faction = door:GetNetVar("faction") -- If the door has a faction set which the client is a member of, allow access. if (faction and client:Team() == faction) then return true end local class = door:GetNetVar("class") -- If the door has a faction set which the client is a member of, allow access. local classData = ix.class.list[class] local charClass = client:GetCharacter():GetClass() local classData2 = ix.class.list[charClass] if (class and classData and classData2) then if (classData.team) then if (classData.team != classData2.team) then return false end else if (charClass != class) then return false end end return true end end function PLUGIN:PostPlayerLoadout(client) client:Give("ix_keys") end function PLUGIN:ShowTeam(client) local data = {} data.start = client:GetShootPos() data.endpos = data.start + client:GetAimVector() * 96 data.filter = client local trace = util.TraceLine(data) local entity = trace.Entity if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("faction") and !entity:GetNetVar("class")) then if (entity:CheckDoorAccess(client, DOOR_TENANT)) then local door = entity if (IsValid(door.ixParent)) then door = door.ixParent end net.Start("ixDoorMenu") net.WriteEntity(door) net.WriteTable(door.ixAccess) net.WriteEntity(entity) net.Send(client) elseif (!IsValid(entity:GetDTEntity(0))) then ix.command.Run(client, "doorbuy") else client:NotifyLocalized("notAllowed") end return true end end function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar) if (prevChar) then local doors = prevChar:GetVar("doors") or {} for _, v in ipairs(doors) do if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then v:RemoveDoorAccessData() end end prevChar:SetVar("doors", nil) end end function PLUGIN:PlayerDisconnected(client) local character = client:GetCharacter() if (character) then local doors = character:GetVar("doors") or {} for _, v in ipairs(doors) do if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then v:RemoveDoorAccessData() end end character:SetVar("doors", nil) end end net.Receive("ixDoorPermission", function(length, client) local door = net.ReadEntity() local target = net.ReadEntity() local access = net.ReadUInt(4) if (IsValid(target) and target:GetCharacter() and door.ixAccess and door:GetDTEntity(0) == client and target != client) then access = math.Clamp(access or 0, DOOR_NONE, DOOR_TENANT) if (access == door.ixAccess[target]) then return end door.ixAccess[target] = access local recipient = {} for k, v in pairs(door.ixAccess) do if (v > DOOR_GUEST) then recipient[#recipient + 1] = k end end if (#recipient > 0) then net.Start("ixDoorPermission") net.WriteEntity(door) net.WriteEntity(target) net.WriteUInt(access, 4) net.Send(recipient) end end end) ================================================ FILE: plugins/logging.lua ================================================ PLUGIN.name = "Logging" PLUGIN.author = "Black Tea" PLUGIN.description = "You can modfiy the logging text/lists on this plugin." if (SERVER) then local L = Format ix.log.AddType("chat", function(client, ...) local arg = {...} return L("[%s] %s: %s", arg[1], client:Name(), arg[2]) end) ix.log.AddType("command", function(client, ...) local arg = {...} if (arg[2] and #arg[2] > 0) then return L("%s used command '%s %s'.", client:Name(), arg[1], arg[2]) else return L("%s used command '%s'.", client:Name(), arg[1]) end end) ix.log.AddType("cfgSet", function(client, ...) local arg = {...} return L("%s set %s to '%s'.", client:Name(), arg[1], arg[2]) end, FLAG_DANGER) ix.log.AddType("connect", function(client, ...) return L("%s has connected.", client:SteamName()) end, FLAG_NORMAL) ix.log.AddType("disconnect", function(client, ...) if (client:IsTimingOut()) then return L("%s (%s) has disconnected (timed out).", client:SteamName(), client:SteamID()) else return L("%s (%s) has disconnected.", client:SteamName(), client:SteamID()) end end, FLAG_NORMAL) ix.log.AddType("charCreate", function(client, ...) local arg = {...} return L("%s created the character '%s'", client:SteamName(), arg[1]) end, FLAG_SERVER) ix.log.AddType("charLoad", function(client, ...) local arg = {...} return L("%s loaded the character '%s'", client:SteamName(), arg[1]) end, FLAG_SERVER) ix.log.AddType("charDelete", function(client, ...) local arg = {...} return L("%s (%s) deleted character '%s'", client:SteamName(), client:SteamID(), arg[1]) end, FLAG_SERVER) ix.log.AddType("itemAction", function(client, ...) local arg = {...} local item = arg[2] return L("%s ran '%s' on item '%s' (#%s)", client:Name(), arg[1], item:GetName(), item:GetID()) end, FLAG_NORMAL) ix.log.AddType("itemDestroy", function(client, itemName, itemID) local name = client:GetName() ~= "" and client:GetName() or client:GetClass() return L("%s destroyed a '%s' #%d.", name, itemName, itemID) end, FLAG_WARNING) ix.log.AddType("shipmentTake", function(client, ...) local arg = {...} return L("%s took '%s' from the shipment", client:Name(), arg[1]) end, FLAG_WARNING) ix.log.AddType("shipmentOrder", function(client, ...) return L("%s ordered a shipment", client:Name()) end, FLAG_SUCCESS) ix.log.AddType("buy", function(client, ...) local arg = {...} return L("%s purchased '%s' from the NPC", client:Name(), arg[1]) end, FLAG_SUCCESS) ix.log.AddType("buydoor", function(client, ...) return L("%s has purchased a door.", client:Name()) end, FLAG_SUCCESS) ix.log.AddType("selldoor", function(client, ...) return L("%s has sold a door.", client:Name()) end, FLAG_SUCCESS) ix.log.AddType("playerHurt", function(client, ...) local arg = {...} return L("%s has taken %d damage from %s.", client:Name(), arg[1], arg[2]) end, FLAG_WARNING) ix.log.AddType("playerDeath", function(client, ...) local arg = {...} return L("%s has killed %s%s.", arg[1], client:Name(), arg[2] and (" with " .. arg[2]) or "") end, FLAG_DANGER) ix.log.AddType("money", function(client, amount) return L("%s has %s %s.", client:Name(), amount < 0 and "lost" or "gained", ix.currency.Get(math.abs(amount))) end, FLAG_SUCCESS) ix.log.AddType("inventoryAdd", function(client, characterName, itemName, itemID) return L("%s has gained a '%s' #%d.", characterName, itemName, itemID) end, FLAG_WARNING) ix.log.AddType("inventoryRemove", function(client, characterName, itemName, itemID) return L("%s has lost a '%s' #%d.", characterName, itemName, itemID) end, FLAG_WARNING) ix.log.AddType("storageMoneyTake", function(client, entity, amount, total) local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName() return string.format("%s has taken %d %s from '%s' #%d (%d %s left).", client:GetName(), amount, ix.currency.plural, name, entity:GetInventory():GetID(), total, ix.currency.plural) end) ix.log.AddType("storageMoneyGive", function(client, entity, amount, total) local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName() return string.format("%s has given %d %s to '%s' #%d (%d %s left).", client:GetName(), amount, ix.currency.plural, name, entity:GetInventory():GetID(), total, ix.currency.plural) end) ix.log.AddType("roll", function(client, value, max) return string.format("%s rolled %d out of %d.", client:Name(), value, max) end) ix.log.AddType("pluginLoaded", function(client, uniqueID) return string.format("%s has enabled the %s plugin for next restart.", client:GetName(), uniqueID) end) ix.log.AddType("pluginUnloaded", function(client, uniqueID) return string.format("%s has disabled the %s plugin for next restart.", client:GetName(), uniqueID) end) function PLUGIN:PlayerInitialSpawn(client) ix.log.Add(client, "connect") end function PLUGIN:PlayerDisconnected(client) ix.log.Add(client, "disconnect") end function PLUGIN:OnCharacterCreated(client, character) ix.log.Add(client, "charCreate", character:GetName()) end function PLUGIN:CharacterLoaded(character) local client = character:GetPlayer() ix.log.Add(client, "charLoad", character:GetName()) end function PLUGIN:PreCharacterDeleted(client, character) ix.log.Add(client, "charDelete", character:GetName()) end function PLUGIN:ShipmentItemTaken(client, itemClass, amount) local itemTable = ix.item.list[itemClass] ix.log.Add(client, "shipmentTake", itemTable:GetName()) end function PLUGIN:CreateShipment(client, shipmentEntity) ix.log.Add(client, "shipmentOrder") end function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell) end function PLUGIN:PlayerInteractItem(client, action, item) if (isentity(item)) then if (IsValid(item)) then local itemID = item.ixItemID item = ix.item.instances[itemID] else return end elseif (isnumber(item)) then item = ix.item.instances[item] end if (!item) then return end ix.log.Add(client, "itemAction", action, item) end function PLUGIN:InventoryItemAdded(oldInv, inventory, item) if (!inventory.owner or (oldInv and oldInv.owner == inventory.owner)) then return end local character = ix.char.loaded[inventory.owner] ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), item:GetName(), item:GetID()) if (item.isBag) then local bagInventory = item:GetInventory() if (!bagInventory) then return end for k, _ in bagInventory:Iter() do ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), k:GetName(), k:GetID()) end end end function PLUGIN:InventoryItemRemoved(inventory, item) if (!inventory.owner) then return end local character = ix.char.loaded[inventory.owner] ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), item:GetName(), item:GetID()) if (item.isBag) then for k, _ in item:GetInventory():Iter() do ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), k:GetName(), k:GetID()) end end end end ================================================ FILE: plugins/mapscene.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Map Scenes" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds areas of the map that are visible during character selection." PLUGIN.scenes = PLUGIN.scenes or {} local x3, y3 = 0, 0 local realOrigin = Vector(0, 0, 0) local realAngles = Angle(0, 0, 0) local view = {} if (CLIENT) then PLUGIN.ordered = PLUGIN.ordered or {} function PLUGIN:CalcView(client, origin, angles, fov) local scenes = self.scenes if (IsValid(ix.gui.characterMenu) and !IsValid(ix.gui.menu) and !ix.gui.characterMenu:IsClosing() and !table.IsEmpty(scenes)) then local key = self.index local value = scenes[self.index] if (!self.index or !value) then value, key = table.Random(scenes) self.index = key end if (self.orderedIndex or value.origin or isvector(key)) then local curTime = CurTime() self.orderedIndex = self.orderedIndex or 1 local ordered = self.ordered[self.orderedIndex] if (ordered) then key = ordered[1] value = ordered[2] end if (!self.startTime) then self.startTime = curTime self.finishTime = curTime + 30 end local fraction = math.min(math.TimeFraction(self.startTime, self.finishTime, CurTime()), 1) if (value) then realOrigin = LerpVector(fraction, key, value[1]) realAngles = LerpAngle(fraction, value[2], value[3]) end if (fraction >= 1) then self.startTime = curTime self.finishTime = curTime + 30 if (ordered) then self.orderedIndex = self.orderedIndex + 1 if (self.orderedIndex > #self.ordered) then self.orderedIndex = 1 end else local keys = {} for k, _ in pairs(scenes) do if (isvector(k)) then keys[#keys + 1] = k end end self.index = keys[ math.random( #keys ) ] end end elseif (value) then realOrigin = value[1] realAngles = value[2] end local x, y = gui.MousePos() local x2, y2 = surface.ScreenWidth() * 0.5, surface.ScreenHeight() * 0.5 local frameTime = FrameTime() * 0.5 y3 = Lerp(frameTime, y3, math.Clamp((y - y2) / y2, -1, 1) * -6) x3 = Lerp(frameTime, x3, math.Clamp((x - x2) / x2, -1, 1) * 6) view.origin = realOrigin + realAngles:Up()*y3 + realAngles:Right()*x3 view.angles = realAngles + Angle(y3 * -0.5, x3 * -0.5, 0) return view end end function PLUGIN:PreDrawViewModel(viewModel, client, weapon) if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) then return true end end net.Receive("ixMapSceneAdd", function() local data = net.ReadTable() PLUGIN.scenes[#PLUGIN.scenes + 1] = data end) net.Receive("ixMapSceneRemove", function() local index = net.ReadUInt(16) PLUGIN.scenes[index] = nil end) net.Receive("ixMapSceneAddPair", function() local data = net.ReadTable() local origin = net.ReadVector() PLUGIN.scenes[origin] = data table.insert(PLUGIN.ordered, {origin, data}) end) net.Receive("ixMapSceneRemovePair", function() local key = net.ReadVector() PLUGIN.scenes[key] = nil for k, v in ipairs(PLUGIN.ordered) do if (v[1] == key) then table.remove(PLUGIN.ordered, k) break end end end) net.Receive("ixMapSceneSync", function() local length = net.ReadUInt(32) local data = net.ReadData(length) local uncompressed = util.Decompress(data) if (!uncompressed) then ErrorNoHalt("[Helix] Unable to decompress map scene data!\n") return end -- Set the list of texts to the ones provided by the server. PLUGIN.scenes = util.JSONToTable(uncompressed) for k, v in pairs(PLUGIN.scenes) do if (v.origin or isvector(k)) then table.insert(PLUGIN.ordered, {v.origin and v.origin or k, v}) end end end) else util.AddNetworkString("ixMapSceneSync") util.AddNetworkString("ixMapSceneAdd") util.AddNetworkString("ixMapSceneRemove") util.AddNetworkString("ixMapSceneAddPair") util.AddNetworkString("ixMapSceneRemovePair") function PLUGIN:SaveScenes() self:SetData(self.scenes) end function PLUGIN:LoadData() self.scenes = self:GetData() or {} end function PLUGIN:PlayerInitialSpawn(client) local json = util.TableToJSON(self.scenes) local compressed = util.Compress(json) local length = compressed:len() net.Start("ixMapSceneSync") net.WriteUInt(length, 32) net.WriteData(compressed, length) net.Send(client) end function PLUGIN:AddScene(position, angles, position2, angles2) local data if (position2) then data = {origin=position, position2, angles, angles2} self.scenes[#self.scenes + 1] = data net.Start("ixMapSceneAddPair") net.WriteTable(data) net.WriteVector(position) net.Broadcast() else data = {position, angles} self.scenes[#self.scenes + 1] = data net.Start("ixMapSceneAdd") net.WriteTable(data) net.Broadcast() end self:SaveScenes() end end ix.command.Add("MapSceneAdd", { description = "@cmdMapSceneAdd", privilege = "Manage Map Scenes", adminOnly = true, arguments = bit.bor(ix.type.bool, ix.type.optional), OnRun = function(self, client, bIsPair) local position, angles = client:EyePos(), client:EyeAngles() -- This scene is in a pair for moving scenes. if (tobool(bIsPair) and !client.ixScnPair) then client.ixScnPair = {position, angles} return "@mapRepeat" else if (client.ixScnPair) then PLUGIN:AddScene(client.ixScnPair[1], client.ixScnPair[2], position, angles) client.ixScnPair = nil else PLUGIN:AddScene(position, angles) end return "@mapAdd" end end }) ix.command.Add("MapSceneRemove", { description = "@cmdMapSceneRemove", privilege = "Manage Map Scenes", adminOnly = true, arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, radius) radius = radius or 280 local position = client:GetPos() local i = 0 for k, v in pairs(PLUGIN.scenes) do local delete = false if (isvector(k)) then if (k:Distance(position) <= radius or v[1]:Distance(position) <= radius) then delete = true end elseif (v[1]:Distance(position) <= radius) then delete = true end if (delete) then if (isvector(k)) then net.Start("ixMapSceneRemovePair") net.WriteVector(k) net.Broadcast() else net.Start("ixMapSceneRemove") net.WriteString(k) net.Broadcast() end PLUGIN.scenes[k] = nil i = i + 1 end end if (i > 0) then PLUGIN:SaveScenes() end return "@mapDel", i end }) ================================================ FILE: plugins/observer.lua ================================================ PLUGIN.name = "Observer" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds on to the no-clip mode to prevent intrusion." CAMI.RegisterPrivilege({ Name = "Helix - Observer", MinAccess = "admin" }) ix.option.Add("observerTeleportBack", ix.type.bool, true, { bNetworked = true, category = "observer", hidden = function() return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Observer", nil) end }) if (CLIENT) then ix.option.Add("observerESP", ix.type.bool, true, { category = "observer", hidden = function() return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Observer", nil) end }) local dimDistance = 1024 local aimLength = 128 local barHeight = 2 function PLUGIN:HUDPaint() local client = LocalPlayer() if (ix.option.Get("observerESP", true) and client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle() and CAMI.PlayerHasAccess(client, "Helix - Observer", nil)) then local scrW, scrH = ScrW(), ScrH() for _, v in player.Iterator() do if (v == client or !v:GetCharacter() or client:GetAimVector():Dot((v:GetPos() - client:GetPos()):GetNormal()) < 0.65) then continue end local screenPosition = v:GetPos():ToScreen() local aimPosition = (v:GetPos() + v:GetAimVector() * aimLength):ToScreen() local marginX, marginY = scrH * .1, scrH * .1 local x, y = math.Clamp(screenPosition.x, marginX, scrW - marginX), math.Clamp(screenPosition.y, marginY, scrH - marginY) local aimX, aimY = math.Clamp(aimPosition.x, marginX, scrW - marginX), math.Clamp(aimPosition.y, marginY, scrH - marginY) local teamColor = team.GetColor(v:Team()) local distance = client:GetPos():Distance(v:GetPos()) local factor = 1 - math.Clamp(distance / dimDistance, 0, 1) local size = math.max(10, 32 * factor) local alpha = math.max(255 * factor, 80) local aimAlpha = (1 - factor * 1.5) * 80 surface.SetDrawColor(teamColor.r, teamColor.g, teamColor.b, alpha) surface.SetFont("ixGenericFont") local text = v:Name() local textWidth, textHeight = surface.GetTextSize(text) local barWidth = math.Clamp((v:Health() / v:GetMaxHealth()) * textWidth, 0, textWidth) surface.DrawRect(x - size / 2, y - size / 2, size, size) -- we can assume that if we're using cheap blur, we'd want to save some fps here if (!ix.option.Get("cheapBlur", false)) then local data = {} data.start = client:EyePos() data.endpos = v:EyePos() data.filter = {client, v} if (util.TraceLine(data).Hit) then aimAlpha = alpha else aimAlpha = (1 - factor * 4) * 80 end end if (aimPosition.visible) then surface.SetDrawColor(teamColor.r * 1.2, teamColor.g * 1.2, teamColor.b * 1.2, aimAlpha) surface.DrawLine(x, y, aimX, aimY) surface.DrawLine(x, y + 1, aimX, aimY + 1) end surface.SetDrawColor(teamColor.r * 1.6, teamColor.g * 1.6, teamColor.b * 1.6, alpha) surface.DrawRect(x - barWidth / 2, y - size - textHeight / 2, barWidth, barHeight) ix.util.DrawText(text, x, y - size, ColorAlpha(teamColor, alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, nil, alpha) end end end function PLUGIN:ShouldPopulateEntityInfo(entity) if (IsValid(entity)) then if ((entity:IsPlayer() or IsValid(entity:GetNetVar("player"))) and entity:GetMoveType() == MOVETYPE_NOCLIP) then return false end end end function PLUGIN:DrawPhysgunBeam(client, physgun, enabled, target, bone, hitPos) if (client != LocalPlayer() and client:GetMoveType() == MOVETYPE_NOCLIP) then return false end end function PLUGIN:PrePlayerDraw(client) if (client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) then return true end end else ix.log.AddType("observerEnter", function(client, ...) return string.format("%s entered observer.", client:Name()) end) ix.log.AddType("observerExit", function(client, ...) if (ix.option.Get(client, "observerTeleportBack", true)) then return string.format("%s exited observer.", client:Name()) else return string.format("%s exited observer at their location.", client:Name()) end end) function PLUGIN:CanPlayerEnterObserver(client) if (CAMI.PlayerHasAccess(client, "Helix - Observer", nil)) then return true end end function PLUGIN:CanPlayerEnterVehicle(client, vehicle, role) if (client:GetMoveType() == MOVETYPE_NOCLIP) then return false end end function PLUGIN:PlayerNoClip(client, state) if (hook.Run("CanPlayerEnterObserver", client)) then if (state) then client.ixObsData = {client:GetPos(), client:EyeAngles()} -- Hide them so they are not visible. client:SetNoDraw(true) client:SetNotSolid(true) client:DrawWorldModel(false) client:DrawShadow(false) client:GodEnable() client:SetNoTarget(true) hook.Run("OnPlayerObserve", client, state) else if (client.ixObsData) then -- Move they player back if they want. if (ix.option.Get(client, "observerTeleportBack", true)) then local position, angles = client.ixObsData[1], client.ixObsData[2] -- Do it the next frame since the player can not be moved right now. timer.Simple(0, function() client:SetPos(position) client:SetEyeAngles(angles) client:SetVelocity(Vector(0, 0, 0)) end) end client.ixObsData = nil end -- Make the player visible again. client:SetNoDraw(false) client:SetNotSolid(false) client:DrawWorldModel(true) client:DrawShadow(true) client:GodDisable() client:SetNoTarget(false) hook.Run("OnPlayerObserve", client, state) end return true end end function PLUGIN:OnPlayerObserve(client, state) if (state) then ix.log.Add(client, "observerEnter") else ix.log.Add(client, "observerExit") end end end ================================================ FILE: plugins/pac.lua ================================================ -- luacheck: globals pac pace -- This Library is just for PAC3 Integration. -- You must install PAC3 to make this library work. PLUGIN.name = "PAC3 Integration" PLUGIN.author = "Black Tea" PLUGIN.description = "PAC3 integration for item parts." if (!pace) then return end ix.pac = ix.pac or {} ix.pac.list = ix.pac.list or {} CAMI.RegisterPrivilege({ Name = "Helix - Manage PAC", MinAccess = "superadmin" }) -- this stores pac3 part information to plugin's table' function ix.pac.RegisterPart(id, outfit) ix.pac.list[id] = outfit end -- Fixing the PAC3's default stuffs to fit on Helix. if (CLIENT) then -- Disable the "in editor" HUD element. hook.Add("InitializedPlugins", "PAC3Fixer", function() hook.Remove("HUDPaint", "pac_in_editor") end) -- Remove PAC3 LoadParts function pace.LoadParts(name, clear, override_part) end -- Prohibits players from deleting their own PAC3 outfit. concommand.Add("pac_clear_parts", function() RunConsoleCommand("pac_restart") end) -- you need the proper permission to open the editor function PLUGIN:PrePACEditorOpen() if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage PAC", nil)) then return false end end end function PLUGIN:pac_CanWearParts(client) if (!CAMI.PlayerHasAccess(client, "Helix - Manage PAC", nil)) then return false end end local meta = FindMetaTable("Player") -- Get Player's PAC3 Parts. function meta:GetParts() if (!pac) then return end return self:GetNetVar("parts", {}) end if (SERVER) then util.AddNetworkString("ixPartWear") util.AddNetworkString("ixPartRemove") util.AddNetworkString("ixPartReset") function meta:AddPart(uniqueID, item) if (!pac) then return end local curParts = self:GetParts() -- wear the parts. net.Start("ixPartWear") net.WriteEntity(self) net.WriteString(uniqueID) net.Broadcast() curParts[uniqueID] = true self:SetNetVar("parts", curParts) end function meta:RemovePart(uniqueID) if (!pac) then return end local curParts = self:GetParts() -- remove the parts. net.Start("ixPartRemove") net.WriteEntity(self) net.WriteString(uniqueID) net.Broadcast() curParts[uniqueID] = nil self:SetNetVar("parts", curParts) end function meta:ResetParts() if (!pac) then return end net.Start("ixPartReset") net.WriteEntity(self) net.WriteTable(self:GetParts()) net.Broadcast() self:SetNetVar("parts", {}) end function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar) -- Reset the characters parts. local curParts = client:GetParts() if (curParts) then client:ResetParts() end -- After resetting all PAC3 outfits, wear all equipped PAC3 outfits. if (curChar) then local inv = curChar:GetInventory() for k, _ in inv:Iter() do if (k:GetData("equip") == true and k.pacData) then client:AddPart(k.uniqueID, k) end end end end function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon) local oldItem = IsValid(oldWeapon) and oldWeapon.ixItem local newItem = IsValid(newWeapon) and newWeapon.ixItem if (oldItem and oldItem.isWeapon and oldItem:GetData("equip") and oldItem.pacData) then oldItem:WearPAC(client) end if (newItem and newItem.isWeapon and newItem.pacData) then newItem:RemovePAC(client) end end -- Hides PAC parts when a player enters observer. function PLUGIN:OnPlayerObserve(client, state) local curParts = client:GetParts() -- Remove all the parts if (curParts) then client:ResetParts() end -- If exiting of observer, re-add all parts. if (!state) then local character = client:GetCharacter() local inventory = character:GetInventory() for k, _ in inventory:Iter() do if (k:GetData("equip") == true and k.pacData) then client:AddPart(k.uniqueID, k) end end end end else local function AttachPart(client, uniqueID) local itemTable = ix.item.list[uniqueID] local pacData = ix.pac.list[uniqueID] if (pacData) then if (itemTable and itemTable.pacAdjust) then pacData = table.Copy(pacData) pacData = itemTable:pacAdjust(pacData, client) end if (isfunction(client.AttachPACPart)) then client:AttachPACPart(pacData) else pac.SetupENT(client) timer.Simple(0.1, function() if (IsValid(client) and isfunction(client.AttachPACPart)) then client:AttachPACPart(pacData) end end) end end end local function RemovePart(client, uniqueID) local itemTable = ix.item.list[uniqueID] local pacData = ix.pac.list[uniqueID] if (pacData) then if (itemTable and itemTable.pacAdjust) then pacData = table.Copy(pacData) pacData = itemTable:pacAdjust(pacData, client) end if (isfunction(client.RemovePACPart)) then client:RemovePACPart(pacData) else pac.SetupENT(client) end end end hook.Add("Think", "ix_pacupdate", function() if (!pac) then hook.Remove("Think", "ix_pacupdate") return end if (IsValid(pac.LocalPlayer)) then for _, v in player.Iterator() do local character = v:GetCharacter() if (character) then local parts = v:GetParts() for k2, _ in pairs(parts) do AttachPart(v, k2) end end end hook.Remove("Think", "ix_pacupdate") end end) net.Receive("ixPartWear", function(length) if (!pac) then return end local wearer = net.ReadEntity() local uid = net.ReadString() if (!wearer.pac_owner) then pac.SetupENT(wearer) end AttachPart(wearer, uid) end) net.Receive("ixPartRemove", function(length) if (!pac) then return end local wearer = net.ReadEntity() local uid = net.ReadString() if (!wearer.pac_owner) then pac.SetupENT(wearer) end RemovePart(wearer, uid) end) net.Receive("ixPartReset", function(length) if (!pac) then return end local wearer = net.ReadEntity() local uidList = net.ReadTable() if (!wearer.pac_owner) then pac.SetupENT(wearer) end for k, _ in pairs(uidList) do RemovePart(wearer, k) end end) function PLUGIN:DrawPlayerRagdoll(entity) local ply = entity.objCache if (IsValid(ply)) then if (!entity.overridePAC3) then if ply.pac_parts then for _, part in pairs(ply.pac_parts) do if part.last_owner and part.last_owner:IsValid() then hook.Run("OnPAC3PartTransferred", part) part:SetOwner(entity) part.last_owner = entity end end end ply.pac_playerspawn = pac.RealTime -- used for events entity.overridePAC3 = true end end end function PLUGIN:OnEntityCreated(entity) local class = entity:GetClass() -- For safe progress, I skip one frame. timer.Simple(0.01, function() if (class == "prop_ragdoll") then if (entity:GetNetVar("player")) then entity.RenderOverride = function() entity.objCache = entity:GetNetVar("player") entity:DrawModel() hook.Run("DrawPlayerRagdoll", entity) end end end if (class:find("HL2MPRagdoll")) then for _, v in player.Iterator() do if (v:GetRagdollEntity() == entity) then entity.objCache = v end end entity.RenderOverride = function() entity:DrawModel() hook.Run("DrawPlayerRagdoll", entity) end end end) end function PLUGIN:DrawCharacterOverview() if (!pac) then return end if (LocalPlayer().pac_outfits) then pac.RenderOverride(LocalPlayer(), "opaque") pac.RenderOverride(LocalPlayer(), "translucent", true) end end function PLUGIN:DrawHelixModelView(panel, ent) if (!pac) then return end if (LocalPlayer():GetCharacter()) then pac.RenderOverride(ent, "opaque") pac.RenderOverride(ent, "translucent", true) end end end function PLUGIN:InitializedPlugins() local items = ix.item.list for _, v in pairs(items) do if (v.pacData) then ix.pac.list[v.uniqueID] = v.pacData end end end ================================================ FILE: plugins/permakill.lua ================================================ PLUGIN.name = "Permakill" PLUGIN.author = "Thadah Denyse" PLUGIN.description = "Adds permanent death in the server options." ix.config.Add("permakill", false, "Whether or not permakill is activated on the server.", nil, { category = "Permakill" }) ix.config.Add("permakillWorld", false, "Whether or not world and self damage produce permanent death.", nil, { category = "Permakill" }) function PLUGIN:PlayerDeath(client, inflictor, attacker) local character = client:GetCharacter() if (ix.config.Get("permakill") and character) then if (hook.Run("ShouldPermakillCharacter", client, character, inflictor, attacker) == false) then return end if (ix.config.Get("permakillWorld") and (client == attacker or inflictor:IsWorld())) then return end character:SetData("permakilled", true) end end function PLUGIN:PlayerSpawn(client) local character = client:GetCharacter() if (ix.config.Get("permakill") and character and character:GetData("permakilled")) then character:Ban() character:SetData("permakilled") end end ================================================ FILE: plugins/persistence.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Persistence" PLUGIN.description = "Define entities to persist through restarts." PLUGIN.author = "alexgrist" PLUGIN.stored = PLUGIN.stored or {} local function GetRealModel(entity) return entity:GetClass() == "prop_effect" and entity.AttachedEntity:GetModel() or entity:GetModel() end properties.Add("persist", { MenuLabel = "#makepersistent", Order = 400, MenuIcon = "icon16/link.png", Filter = function(self, entity, client) if (entity:IsPlayer() or entity:IsVehicle() or entity.bNoPersist) then return false end if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end return !entity:GetNetVar("Persistent", false) end, Action = function(self, entity) self:MsgStart() net.WriteEntity(entity) self:MsgEnd() end, Receive = function(self, length, client) local entity = net.ReadEntity() if (!IsValid(entity)) then return end if (!self:Filter(entity, client)) then return end PLUGIN.stored[#PLUGIN.stored + 1] = entity entity:SetNetVar("Persistent", true) ix.log.Add(client, "persist", GetRealModel(entity), true) end }) properties.Add("persist_end", { MenuLabel = "#stoppersisting", Order = 400, MenuIcon = "icon16/link_break.png", Filter = function(self, entity, client) if (entity:IsPlayer()) then return false end if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end return entity:GetNetVar("Persistent", false) end, Action = function(self, entity) self:MsgStart() net.WriteEntity(entity) self:MsgEnd() end, Receive = function(self, length, client) local entity = net.ReadEntity() if (!IsValid(entity)) then return end if (!self:Filter(entity, client)) then return end for k, v in ipairs(PLUGIN.stored) do if (v == entity) then table.remove(PLUGIN.stored, k) break end end entity:SetNetVar("Persistent", false) ix.log.Add(client, "persist", GetRealModel(entity), false) end }) function PLUGIN:PhysgunPickup(client, entity) if (entity:GetNetVar("Persistent", false)) then return false end end if (SERVER) then function PLUGIN:LoadData() local entities = self:GetData() or {} for _, v in ipairs(entities) do local entity = ents.Create(v.Class) if (IsValid(entity)) then entity:SetPos(v.Pos) entity:SetAngles(v.Angle) entity:SetModel(v.Model) entity:SetSkin(v.Skin) entity:SetColor(v.Color) entity:SetMaterial(v.Material) entity:Spawn() entity:Activate() if (v.bNoCollision) then entity:SetCollisionGroup(COLLISION_GROUP_WORLD) end if (istable(v.BodyGroups)) then for k2, v2 in pairs(v.BodyGroups) do entity:SetBodygroup(k2, v2) end end if (istable(v.SubMaterial)) then for k2, v2 in pairs(v.SubMaterial) do if (!isnumber(k2) or !isstring(v2)) then continue end entity:SetSubMaterial(k2 - 1, v2) end end local physicsObject = entity:GetPhysicsObject() if (IsValid(physicsObject)) then physicsObject:EnableMotion(v.Movable) end self.stored[#self.stored + 1] = entity entity:SetNetVar("Persistent", true) end end end function PLUGIN:SaveData() local entities = {} for _, v in ipairs(self.stored) do if (IsValid(v)) then local data = {} data.Class = v.ClassOverride or v:GetClass() data.Pos = v:GetPos() data.Angle = v:GetAngles() data.Model = GetRealModel(v) data.Skin = v:GetSkin() data.Color = v:GetColor() data.Material = v:GetMaterial() data.bNoCollision = v:GetCollisionGroup() == COLLISION_GROUP_WORLD local materials = v:GetMaterials() if (istable(materials)) then data.SubMaterial = {} for k2, _ in pairs(materials) do if (v:GetSubMaterial(k2 - 1) != "") then data.SubMaterial[k2] = v:GetSubMaterial(k2 - 1) end end end local bodyGroups = v:GetBodyGroups() if (istable(bodyGroups)) then data.BodyGroups = {} for _, v2 in pairs(bodyGroups) do if (v:GetBodygroup(v2.id) > 0) then data.BodyGroups[v2.id] = v:GetBodygroup(v2.id) end end end local physicsObject = v:GetPhysicsObject() if (IsValid(physicsObject)) then data.Movable = physicsObject:IsMoveable() end entities[#entities + 1] = data end end self:SetData(entities) end ix.log.AddType("persist", function(client, ...) local arg = {...} return string.format("%s has %s persistence for '%s'.", client:Name(), arg[2] and "enabled" or "disabled", arg[1]) end) end ================================================ FILE: plugins/propprotect.lua ================================================ PLUGIN.name = "Basic Prop Protection" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds a simple prop protection system." CAMI.RegisterPrivilege({ Name = "Helix - Bypass Prop Protection", MinAccess = "admin" }) local PROP_BLACKLIST = { ["models/props_combine/combinetrain02b.mdl"] = true, ["models/props_combine/combinetrain02a.mdl"] = true, ["models/props_combine/combinetrain01.mdl"] = true, ["models/cranes/crane_frame.mdl"] = true, ["models/props_junk/trashdumpster02.mdl"] = true, ["models/props_c17/oildrum001_explosive.mdl"] = true, ["models/props_canal/canal_bridge02.mdl"] = true, ["models/props_canal/canal_bridge01.mdl"] = true, ["models/props_canal/canal_bridge03a.mdl"] = true, ["models/props_canal/canal_bridge03b.mdl"] = true, ["models/props_wasteland/cargo_container01.mdl"] = true, ["models/props_wasteland/cargo_container01c.mdl"] = true, ["models/props_wasteland/cargo_container01b.mdl"] = true, ["models/props_combine/combine_mine01.mdl"] = true, ["models/props_junk/glassjug01.mdl"] = true, ["models/props_c17/paper01.mdl"] = true, ["models/props_junk/garbage_takeoutcarton001a.mdl"] = true, ["models/props_c17/trappropeller_engine.mdl"] = true, ["models/props/cs_office/microwave.mdl"] = true, ["models/items/item_item_crate.mdl"] = true, ["models/props_junk/gascan001a.mdl"] = true, ["models/props_c17/consolebox01a.mdl"] = true, ["models/props_buildings/building_002a.mdl"] = true, ["models/props_phx/mk-82.mdl"] = true, ["models/props_phx/cannonball.mdl"] = true, ["models/props_phx/ball.mdl"] = true, ["models/props_phx/amraam.mdl"] = true, ["models/props_phx/misc/flakshell_big.mdl"] = true, ["models/props_phx/ww2bomb.mdl"] = true, ["models/props_phx/torpedo.mdl"] = true, ["models/props/de_train/biohazardtank.mdl"] = true, ["models/props_buildings/project_building01.mdl"] = true, ["models/props_combine/prison01c.mdl"] = true, ["models/props/cs_militia/silo_01.mdl"] = true, ["models/props_phx/huge/evildisc_corp.mdl"] = true, ["models/props_phx/misc/potato_launcher_explosive.mdl"] = true, ["models/props_combine/combine_citadel001.mdl"] = true, ["models/props_phx/oildrum001_explosive.mdl"] = true, ["models/props_junk/wood_crate01_explosive.mdl"] = true, ["models/props_junk/propane_tank001a.mdl"] = true, ["models/props_explosive/explosive_butane_can.mdl"] = true, ["models/props_explosive/explosive_butane_can02.mdl"] = true } if (SERVER) then ix.log.AddType("spawnProp", function(client, ...) local arg = {...} return string.format("%s has spawned '%s'.", client:Name(), arg[1]) end) ix.log.AddType("spawnEntity", function(client, ...) local arg = {...} return string.format("%s has spawned a '%s'.", client:Name(), arg[1]) end) function PLUGIN:PlayerSpawnObject(client, model, entity) if ((client.ixNextSpawn or 0) < CurTime()) then client.ixNextSpawn = CurTime() + 0.75 else return false end if (!client:IsAdmin() and PROP_BLACKLIST[model:lower()]) then return false end end function PLUGIN:PhysgunPickup(client, entity) local characterID = client:GetCharacter():GetID() if (entity:GetNetVar("owner", 0) != characterID and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end function PLUGIN:OnPhysgunReload(weapon, client) local characterID = client:GetCharacter():GetID() local trace = client:GetEyeTrace() if (IsValid(trace.Entity) and trace.Entity:GetNetVar("owner", 0) != characterID and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end function PLUGIN:CanProperty(client, property, entity) local characterID = client:GetCharacter():GetID() if (entity:GetNetVar("owner", 0) != characterID and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end function PLUGIN:CanTool(client, trace, tool) local entity = trace.Entity local characterID = client:GetCharacter():GetID() if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end function PLUGIN:PlayerSpawnedProp(client, model, entity) ix.log.Add(client, "spawnProp", model) end PLUGIN.PlayerSpawnedEffect = PLUGIN.PlayerSpawnedProp PLUGIN.PlayerSpawnedRagdoll = PLUGIN.PlayerSpawnedProp function PLUGIN:PlayerSpawnedNPC(client, entity) ix.log.Add(client, "spawnEntity", entity) end PLUGIN.PlayerSpawnedSWEP = PLUGIN.PlayerSpawnedNPC PLUGIN.PlayerSpawnedSENT = PLUGIN.PlayerSpawnedNPC PLUGIN.PlayerSpawnedVehicle = PLUGIN.PlayerSpawnedNPC else function PLUGIN:PhysgunPickup(client, entity) if (entity:GetNetVar("owner", 0) != client:GetCharacter():GetID() and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end function PLUGIN:CanProperty(client, property, entity) local characterID = client:GetCharacter():GetID() if (entity:GetNetVar("owner", 0) != characterID and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end function PLUGIN:CanTool(client, trace, tool) local entity = trace.Entity local characterID = client:GetCharacter():GetID() if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then return false end end end ================================================ FILE: plugins/recognition.lua ================================================ PLUGIN.name = "Recognition" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds the ability to recognize people." do local character = ix.meta.character if (SERVER) then function character:Recognize(id) if (!isnumber(id) and id.GetID) then id = id:GetID() end local recognized = self:GetData("rgn", "") if (recognized != "" and recognized:find(","..id..",")) then return false end self:SetData("rgn", recognized..","..id..",") return true end end function character:DoesRecognize(id) if (!isnumber(id) and id.GetID) then id = id:GetID() end return hook.Run("IsCharacterRecognized", self, id) end function PLUGIN:IsCharacterRecognized(char, id) if (char.id == id) then return true end local other = ix.char.loaded[id] if (other) then local faction = ix.faction.indices[other:GetFaction()] if (faction and faction.isGloballyRecognized) then return true end end local recognized = char:GetData("rgn", "") if (recognized != "" and recognized:find(","..id..",")) then return true end end end if (CLIENT) then CHAT_RECOGNIZED = CHAT_RECOGNIZED or {} CHAT_RECOGNIZED["ic"] = true CHAT_RECOGNIZED["y"] = true CHAT_RECOGNIZED["w"] = true CHAT_RECOGNIZED["me"] = true function PLUGIN:IsRecognizedChatType(chatType) if (CHAT_RECOGNIZED[chatType]) then return true end end function PLUGIN:GetCharacterDescription(client) if (client:GetCharacter() and client != LocalPlayer() and LocalPlayer():GetCharacter() and !LocalPlayer():GetCharacter():DoesRecognize(client:GetCharacter()) and !hook.Run("IsPlayerRecognized", client)) then return L"noRecog" end end function PLUGIN:ShouldAllowScoreboardOverride(client) if (ix.config.Get("scoreboardRecognition")) then return true end end function PLUGIN:GetCharacterName(client, chatType) if (client != LocalPlayer()) then local character = client:GetCharacter() local ourCharacter = LocalPlayer():GetCharacter() if (ourCharacter and character and !ourCharacter:DoesRecognize(character) and !hook.Run("IsPlayerRecognized", client)) then if (chatType and hook.Run("IsRecognizedChatType", chatType)) then local description = character:GetDescription() if (#description > 40) then description = description:utf8sub(1, 37).."..." end return "["..description.."]" elseif (!chatType) then return L"unknown" end end end end local function Recognize(level) net.Start("ixRecognize") net.WriteUInt(level, 2) net.SendToServer() end net.Receive("ixRecognizeMenu", function(length) local menu = DermaMenu() menu:AddOption(L"rgnLookingAt", function() Recognize(0) end) menu:AddOption(L"rgnWhisper", function() Recognize(1) end) menu:AddOption(L"rgnTalk", function() Recognize(2) end) menu:AddOption(L"rgnYell", function() Recognize(3) end) menu:Open() menu:MakePopup() menu:Center() end) net.Receive("ixRecognizeDone", function(length) hook.Run("CharacterRecognized") end) function PLUGIN:CharacterRecognized(client, recogCharID) surface.PlaySound("buttons/button17.wav") end else util.AddNetworkString("ixRecognize") util.AddNetworkString("ixRecognizeMenu") util.AddNetworkString("ixRecognizeDone") function PLUGIN:ShowSpare1(client) if (client:GetCharacter()) then net.Start("ixRecognizeMenu") net.Send(client) end end net.Receive("ixRecognize", function(length, client) local level = net.ReadUInt(2) if (isnumber(level)) then local targets = {} if (level < 1) then local entity = client:GetEyeTraceNoCursor().Entity if (IsValid(entity) and entity:IsPlayer() and entity:GetCharacter() and ix.chat.classes.ic:CanHear(client, entity)) then targets[1] = entity end else local class = "w" if (level == 2) then class = "ic" elseif (level == 3) then class = "y" end class = ix.chat.classes[class] for _, v in player.Iterator() do if (client != v and v:GetCharacter() and class:CanHear(client, v)) then targets[#targets + 1] = v end end end if (#targets > 0) then local id = client:GetCharacter():GetID() local i = 0 for _, v in ipairs(targets) do if (v:GetCharacter():Recognize(id)) then i = i + 1 end end if (i > 0) then net.Start("ixRecognizeDone") net.Send(client) hook.Run("CharacterRecognized", client, id) end end end end) end ================================================ FILE: plugins/saveitems.lua ================================================ PLUGIN.name = "Save Items" PLUGIN.author = "Chessnut" PLUGIN.description = "Saves items that were dropped." --[[ function PLUGIN:OnSavedItemLoaded(items) for k, v in ipairs(items) do -- do something end end function PLUGIN:ShouldDeleteSavedItems() return true end ]]-- -- as title says. function PLUGIN:LoadData() local items = self:GetData() if (items) then local idRange = {} local info = {} for _, v in ipairs(items) do idRange[#idRange + 1] = v[1] info[v[1]] = {v[2], v[3], v[4]} end if (#idRange > 0) then if (hook.Run("ShouldDeleteSavedItems") == true) then -- don't spawn saved item and just delete them. local query = mysql:Delete("ix_items") query:WhereIn("item_id", idRange) query:Execute() print("Server Deleted Server Items (does not includes Logical Items)") else local query = mysql:Select("ix_items") query:Select("item_id") query:Select("unique_id") query:Select("data") query:WhereIn("item_id", idRange) query:Callback(function(result) if (istable(result)) then local loadedItems = {} local bagInventories = {} for _, v in ipairs(result) do local itemID = tonumber(v.item_id) local data = util.JSONToTable(v.data or "[]") local uniqueID = v.unique_id local itemTable = ix.item.list[uniqueID] if (itemTable and itemID) then local item = ix.item.New(uniqueID, itemID) item.data = data or {} local itemInfo = info[itemID] local position, angles, bMovable = itemInfo[1], itemInfo[2], true if (isbool(itemInfo[3])) then bMovable = itemInfo[3] end local itemEntity = item:Spawn(position, angles) itemEntity.ixItemID = itemID local physicsObject = itemEntity:GetPhysicsObject() if (IsValid(physicsObject)) then physicsObject:EnableMotion(bMovable) end item.invID = 0 loadedItems[#loadedItems + 1] = item if (item.isBag) then local invType = ix.item.inventoryTypes[uniqueID] bagInventories[item:GetData("id")] = {invType.w, invType.h} end end end -- we need to manually restore bag inventories in the world since they don't have a current owner -- that it can automatically restore along with the character when it's loaded if (!table.IsEmpty(bagInventories)) then ix.inventory.Restore(bagInventories) end hook.Run("OnSavedItemLoaded", loadedItems) -- when you have something in the dropped item. end end) query:Execute() end end end end function PLUGIN:SaveData() local items = {} for _, v in ipairs(ents.FindByClass("ix_item")) do if (v.ixItemID and !v.bTemporary) then local physicsObject = v:GetPhysicsObject() local bMovable = nil if (IsValid(physicsObject)) then bMovable = physicsObject:IsMoveable() end items[#items + 1] = { v.ixItemID, v:GetPos(), v:GetAngles(), bMovable } end end self:SetData(items) end ================================================ FILE: plugins/spawns.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Spawns" PLUGIN.description = "Spawn points for factions and classes." PLUGIN.author = "Chessnut" PLUGIN.spawns = PLUGIN.spawns or {} function PLUGIN:PlayerLoadout(client) local character = client:GetCharacter() if (self.spawns and !table.IsEmpty(self.spawns) and character) then local class = character:GetClass() local points local className = "default" for k, v in ipairs(ix.faction.indices) do if (k == client:Team()) then points = self.spawns[v.uniqueID] or {} break end end if (points) then for _, v in ipairs(ix.class.list) do if (class == v.index) then className = v.uniqueID break end end points = points[className] or points["default"] if (points and !table.IsEmpty(points)) then local position = points[ math.random( #points ) ] client:SetPos(position) end end end end function PLUGIN:LoadData() self.spawns = self:GetData() or {} end function PLUGIN:SaveSpawns() self:SetData(self.spawns) end ix.command.Add("SpawnAdd", { description = "@cmdSpawnAdd", privilege = "Manage Spawn Points", adminOnly = true, arguments = { ix.type.string, bit.bor(ix.type.text, ix.type.optional) }, OnRun = function(self, client, name, class) local info = ix.faction.indices[name:lower()] local info2 local faction if (!info) then for _, v in ipairs(ix.faction.indices) do if (ix.util.StringMatches(v.uniqueID, name) or ix.util.StringMatches(L(v.name, client), name)) then faction = v.uniqueID info = v break end end end if (info) then if (class and class != "") then local found = false for _, v in ipairs(ix.class.list) do if (v.faction == info.index and (v.uniqueID:lower() == class:lower() or ix.util.StringMatches(L(v.name, client), class))) then class = v.uniqueID info2 = v found = true break end end if (!found) then return "@invalidClass" end else class = "default" end PLUGIN.spawns[faction] = PLUGIN.spawns[faction] or {} PLUGIN.spawns[faction][class] = PLUGIN.spawns[faction][class] or {} table.insert(PLUGIN.spawns[faction][class], client:GetPos()) PLUGIN:SaveSpawns() name = L(info.name, client) if (info2) then name = name .. " (" .. L(info2.name, client) .. ")" end return "@spawnAdded", name else return "@invalidFaction" end end }) ix.command.Add("SpawnRemove", { description = "@cmdSpawnRemove", privilege = "Manage Spawn Points", adminOnly = true, arguments = bit.bor(ix.type.number, ix.type.optional), OnRun = function(self, client, radius) radius = radius or 120 local position = client:GetPos() local i = 0 for _, v in pairs(PLUGIN.spawns) do for _, v2 in pairs(v) do for k3, v3 in pairs(v2) do if (v3:Distance(position) <= radius) then v2[k3] = nil i = i + 1 end end end end if (i > 0) then PLUGIN:SaveSpawns() end return "@spawnDeleted", i end }) ================================================ FILE: plugins/spawnsaver.lua ================================================ PLUGIN.name = "Spawn Saver" PLUGIN.author = "Chessnut" PLUGIN.description = "Saves the position of a character." -- Called right before the character has its information save. function PLUGIN:CharacterPreSave(character) -- Get the player from the character. local client = character:GetPlayer() -- Check to see if we can get the player's position. if (IsValid(client)) then local position, eyeAngles = client:GetPos(), client:EyeAngles() -- Use pre-observer position to prevent spawning in the air. if (client.ixObsData) then position, eyeAngles = client.ixObsData[1], client.ixObsData[2] end -- Store the position in the character's data. character:SetData("pos", {position, eyeAngles, game.GetMap()}) end end -- Called after the player's loadout has been set. function PLUGIN:PlayerLoadedCharacter(client, character, lastChar) timer.Simple(0, function() if (IsValid(client)) then -- Get the saved position from the character data. local position = character:GetData("pos") -- Check if the position was set. if (position) then if (position[3] and position[3]:lower() == game.GetMap():lower()) then -- Restore the player to that position. client:SetPos(position[1].x and position[1] or client:GetPos()) client:SetEyeAngles(position[2].p and position[2] or angle_zero) end -- Remove the position data since it is no longer needed. character:SetData("pos", nil) end end end) end ================================================ FILE: plugins/stamina/attributes/sh_end.lua ================================================ ATTRIBUTE.name = "Endurance" ATTRIBUTE.description = "Affects how long you can run for." ================================================ FILE: plugins/stamina/attributes/sh_stm.lua ================================================ ATTRIBUTE.name = "Stamina" ATTRIBUTE.description = "Affects how fast you can run." function ATTRIBUTE:OnSetup(client, value) client:SetRunSpeed(ix.config.Get("runSpeed") + value) end ================================================ FILE: plugins/stamina/sh_plugin.lua ================================================ PLUGIN.name = "Stamina" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds a stamina system to limit running." -- luacheck: push ignore 631 ix.config.Add("staminaDrain", 1, "How much stamina to drain per tick (every quarter second). This is calculated before attribute reduction.", nil, { data = {min = 0, max = 10, decimals = 2}, category = "characters" }) ix.config.Add("staminaRegeneration", 1.75, "How much stamina to regain per tick (every quarter second).", nil, { data = {min = 0, max = 10, decimals = 2}, category = "characters" }) ix.config.Add("staminaCrouchRegeneration", 2, "How much stamina to regain per tick (every quarter second) while crouching.", nil, { data = {min = 0, max = 10, decimals = 2}, category = "characters" }) ix.config.Add("punchStamina", 10, "How much stamina punches use up.", nil, { data = {min = 0, max = 100}, category = "characters" }) -- luacheck: pop local function CalcStaminaChange(client) local character = client:GetCharacter() if (!character or client:GetMoveType() == MOVETYPE_NOCLIP) then return 0 end local walkSpeed = ix.config.Get("walkSpeed") local maxAttributes = ix.config.Get("maxAttributes", 100) local offset if (client:KeyDown(IN_SPEED) and client:GetVelocity():LengthSqr() >= (walkSpeed * walkSpeed) and client:OnGround()) then -- characters could have attribute values greater than max if the config was changed offset = -ix.config.Get("staminaDrain", 1) + math.min(character:GetAttribute("end", 0), maxAttributes) / 100 else offset = client:Crouching() and ix.config.Get("staminaCrouchRegeneration", 2) or ix.config.Get("staminaRegeneration", 1.75) end offset = hook.Run("AdjustStaminaOffset", client, offset) or offset if (CLIENT) then return offset -- for the client we need to return the estimated stamina change else local current = client:GetLocalVar("stm", 0) local value = math.Clamp(current + offset, 0, 100) if (current != value) then client:SetLocalVar("stm", value) if (value == 0 and !client:GetNetVar("brth", false)) then client:SetNetVar("brth", true) character:UpdateAttrib("end", 0.1) character:UpdateAttrib("stm", 0.01) hook.Run("PlayerStaminaLost", client) elseif (value >= 50 and client:GetNetVar("brth", false)) then client:SetNetVar("brth", nil) hook.Run("PlayerStaminaGained", client) end end end end function PLUGIN:SetupMove(client, mv, cmd) if (client:GetNetVar("brth", false)) then mv:SetMaxClientSpeed(client:GetWalkSpeed()) end end if (SERVER) then function PLUGIN:PostPlayerLoadout(client) local uniqueID = "ixStam" .. client:SteamID() timer.Create(uniqueID, 0.25, 0, function() if (!IsValid(client)) then timer.Remove(uniqueID) return end CalcStaminaChange(client) end) end function PLUGIN:CharacterPreSave(character) local client = character:GetPlayer() if (IsValid(client)) then character:SetData("stamina", client:GetLocalVar("stm", 0)) end end function PLUGIN:PlayerLoadedCharacter(client, character) timer.Simple(0.25, function() client:SetLocalVar("stm", character:GetData("stamina", 100)) end) end local playerMeta = FindMetaTable("Player") function playerMeta:RestoreStamina(amount) local current = self:GetLocalVar("stm", 0) local value = math.Clamp(current + amount, 0, 100) self:SetLocalVar("stm", value) end function playerMeta:ConsumeStamina(amount) local current = self:GetLocalVar("stm", 0) local value = math.Clamp(current - amount, 0, 100) self:SetLocalVar("stm", value) end else local predictedStamina = 100 function PLUGIN:Think() local offset = CalcStaminaChange(LocalPlayer()) -- the server check it every 0.25 sec, here we check it every [FrameTime()] seconds offset = math.Remap(FrameTime(), 0, 0.25, 0, offset) if (offset != 0) then predictedStamina = math.Clamp(predictedStamina + offset, 0, 100) end end function PLUGIN:OnLocalVarSet(key, var) if (key != "stm") then return end if (math.abs(predictedStamina - var) > 5) then predictedStamina = var end end ix.bar.Add(function() return predictedStamina / 100 end, Color(200, 200, 40), nil, "stm") end ================================================ FILE: plugins/strength/attributes/sh_str.lua ================================================ ATTRIBUTE.name = "Strength" ATTRIBUTE.description = "A measure of how strong you are." ================================================ FILE: plugins/strength/sh_plugin.lua ================================================ PLUGIN.name = "Strength" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds a strength attribute." if (SERVER) then function PLUGIN:GetPlayerPunchDamage(client, damage, context) if (client:GetCharacter()) then -- Add to the total fist damage. context.damage = context.damage + (client:GetCharacter():GetAttribute("str", 0) * ix.config.Get("strengthMultiplier")) end end function PLUGIN:PlayerThrowPunch(client, trace) if (client:GetCharacter() and IsValid(trace.Entity) and trace.Entity:IsPlayer()) then client:GetCharacter():UpdateAttrib("str", 0.001) end end end -- Configuration for the plugin ix.config.Add("strengthMultiplier", 0.3, "The strength multiplier scale", nil, { data = {min = 0, max = 1.0, decimals = 1}, category = "Strength" }) ================================================ FILE: plugins/thirdperson.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Third Person" PLUGIN.author = "Black Tea" PLUGIN.description = "Enables third person camera usage." ix.config.Add("thirdperson", false, "Allow Thirdperson in the server.", nil, { category = "server" }) if (CLIENT) then local function isHidden() return !ix.config.Get("thirdperson") end ix.option.Add("thirdpersonEnabled", ix.type.bool, false, { category = "thirdperson", hidden = isHidden, OnChanged = function(oldValue, value) hook.Run("ThirdPersonToggled", oldValue, value) end }) ix.option.Add("thirdpersonClassic", ix.type.bool, false, { category = "thirdperson", hidden = isHidden }) ix.option.Add("thirdpersonVertical", ix.type.number, 10, { category = "thirdperson", min = 0, max = 30, hidden = isHidden }) ix.option.Add("thirdpersonHorizontal", ix.type.number, 0, { category = "thirdperson", min = -30, max = 30, hidden = isHidden }) ix.option.Add("thirdpersonDistance", ix.type.number, 50, { category = "thirdperson", min = 0, max = 100, hidden = isHidden }) concommand.Add("ix_togglethirdperson", function() local bEnabled = !ix.option.Get("thirdpersonEnabled", false) ix.option.Set("thirdpersonEnabled", bEnabled) end) local function isAllowed() return ix.config.Get("thirdperson") end local playerMeta = FindMetaTable("Player") local traceMin = Vector(-10, -10, -10) local traceMax = Vector(10, 10, 10) function playerMeta:CanOverrideView() local entity = Entity(self:GetLocalVar("ragdoll", 0)) if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing() and ix.gui.characterMenu:IsVisible()) then return false end if (IsValid(ix.gui.menu) and ix.gui.menu:GetCharacterOverview()) then return false end if (ix.option.Get("thirdpersonEnabled", false) and !IsValid(self:GetVehicle()) and isAllowed() and IsValid(self) and self:GetCharacter() and !self:GetNetVar("actEnterAngle") and !IsValid(entity) and LocalPlayer():Alive() ) then return true end end local view, traceData, traceData2, aimOrigin, crouchFactor, ft, curAng, owner local clmp = math.Clamp crouchFactor = 0 function PLUGIN:CalcView(client, origin, angles, fov) ft = FrameTime() if (client:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then local bNoclip = LocalPlayer():GetMoveType() == MOVETYPE_NOCLIP if ((client:OnGround() and client:KeyDown(IN_DUCK)) or client:Crouching()) then crouchFactor = Lerp(ft*5, crouchFactor, 1) else crouchFactor = Lerp(ft*5, crouchFactor, 0) end curAng = owner.camAng or angle_zero view = {} traceData = {} traceData.start = client:GetPos() + client:GetViewOffset() + curAng:Up() * ix.option.Get("thirdpersonVertical", 10) + curAng:Right() * ix.option.Get("thirdpersonHorizontal", 0) - client:GetViewOffsetDucked() * .5 * crouchFactor traceData.endpos = traceData.start - curAng:Forward() * ix.option.Get("thirdpersonDistance", 50) traceData.filter = client traceData.ignoreworld = bNoclip traceData.mins = traceMin traceData.maxs = traceMax view.origin = util.TraceHull(traceData).HitPos aimOrigin = view.origin view.angles = curAng + client:GetViewPunchAngles() traceData2 = {} traceData2.start = aimOrigin traceData2.endpos = aimOrigin + curAng:Forward() * 65535 traceData2.filter = client traceData2.ignoreworld = bNoclip local bClassic = ix.option.Get("thirdpersonClassic", false) if (bClassic or owner:IsWepRaised() or (owner:KeyDown(bit.bor(IN_FORWARD, IN_BACK, IN_MOVELEFT, IN_MOVERIGHT)) and owner:GetVelocity():Length() >= 10)) then client:SetEyeAngles((util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle()) else local currentAngles = client:EyeAngles() currentAngles.pitch = (util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle().pitch client:SetEyeAngles(currentAngles) end return view end end local diff, fm, sm function PLUGIN:CreateMove(cmd) owner = LocalPlayer() if (owner:CanOverrideView() and owner:GetMoveType() != MOVETYPE_NOCLIP and LocalPlayer():GetViewEntity() == LocalPlayer()) then fm = cmd:GetForwardMove() sm = cmd:GetSideMove() diff = (owner:EyeAngles() - (owner.camAng or Angle(0, 0, 0)))[2] or 0 diff = diff / 90 cmd:SetForwardMove(fm + sm * diff) cmd:SetSideMove(sm + fm * diff) return false end end function PLUGIN:InputMouseApply(cmd, x, y, ang) owner = LocalPlayer() if (!owner.camAng) then owner.camAng = Angle(0, 0, 0) end owner.camAng.p = clmp(math.NormalizeAngle(owner.camAng.p + y / 50), -85, 85) owner.camAng.y = math.NormalizeAngle(owner.camAng.y - x / 50) if (owner:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then return true end end function PLUGIN:ShouldDrawLocalPlayer() if (LocalPlayer():GetViewEntity() == LocalPlayer() and !IsValid(LocalPlayer():GetVehicle())) then return LocalPlayer():CanOverrideView() end end end ================================================ FILE: plugins/typing.lua ================================================ local PLUGIN = PLUGIN PLUGIN.name = "Typing Indicator" PLUGIN.description = "Shows an indicator when someone is typing." PLUGIN.author = "`impulse" PLUGIN.animationTime = 0.5 if (CLIENT) then local standingOffset = Vector(0, 0, 72) local crouchingOffset = Vector(0, 0, 38) local boneOffset = Vector(0, 0, 10) local textColor = Color(250, 250, 250) local shadowColor = Color(66, 66, 66) local currentClass -- we can't rely on matching non-alphanumeric characters (i.e %W) due to patterns matching single bytes and not UTF-8 chars local symbolPattern = "[~`!@#$%%%^&*()_%+%-={}%[%]|;:'\",%./<>?]" function PLUGIN:LoadFonts(font, genericFont) surface.CreateFont("ixTypingIndicator", { font = genericFont, size = 128, extended = true, weight = 1000 }) end function PLUGIN:ChatTextChanged(text) if (!IsValid(LocalPlayer())) then return end local character = LocalPlayer():GetCharacter() if (!character) then return end if (text == "") then currentClass = nil net.Start("ixTypeClass") net.WriteString("") net.SendToServer() return end local newClass = hook.Run("GetTypingIndicator", character, text) if (newClass != currentClass) then currentClass = newClass net.Start("ixTypeClass") net.WriteString(currentClass or "") net.SendToServer() end end function PLUGIN:FinishChat() currentClass = nil net.Start("ixTypeClass") net.WriteString("") net.SendToServer() end function PLUGIN:GetTypingIndicator(character, text) local prefix = text:utf8sub(1, 1) if (!prefix:find(symbolPattern) and text:utf8len() > 1) then return "ic" else local chatType = ix.chat.Parse(nil, text) if (chatType and chatType != "ic") then return !ix.chat.classes[chatType].bNoIndicator and chatType or nil end -- some commands will have their own typing indicator, so we'll make sure we're actually typing out a command first local start, _, commandName = text:find("/(%S+)%s") if (start == 1) then for uniqueID, command in pairs(ix.command.list) do if (command.bNoIndicator) then continue end if (commandName == uniqueID) then return command.indicator and "@" .. command.indicator or "ooc" end end end end end function PLUGIN:GetTypingIndicatorPosition(client) local head for i = 1, client:GetBoneCount() do local name = client:GetBoneName(i) if (string.find(name:lower(), "head")) then head = i break end end local position = head and client:GetBonePosition(head) or (client:Crouching() and crouchingOffset or standingOffset) return position + boneOffset end function PLUGIN:PostDrawTranslucentRenderables() local client = LocalPlayer() local position = client:GetPos() for _, v in player.Iterator() do if (v == client) then continue end local distance = v:GetPos():DistToSqr(position) local moveType = v:GetMoveType() if (!IsValid(v) or !v:Alive() or (moveType != MOVETYPE_WALK and moveType != MOVETYPE_NONE) or !v.ixChatClassText or distance >= v.ixChatClassRange) then continue end local text = v.ixChatClassText local range = v.ixChatClassRange local bAnimation = !ix.option.Get("disableAnimations", false) local fraction if (bAnimation) then local bComplete = v.ixChatClassTween:update(FrameTime()) if (bComplete and !v.ixChatStarted) then v.ixChatClassText = nil v.ixChatClassRange = nil continue end fraction = v.ixChatClassAnimation else fraction = 1 end local angle = EyeAngles() angle:RotateAroundAxis(angle:Forward(), 90) angle:RotateAroundAxis(angle:Right(), 90) cam.Start3D2D(self:GetTypingIndicatorPosition(v), Angle(0, angle.y, 90), 0.05) surface.SetFont("ixTypingIndicator") local _, textHeight = surface.GetTextSize(text) local alpha = bAnimation and ((1 - math.min(distance, range) / range) * 255 * fraction) or 255 draw.SimpleTextOutlined(text, "ixTypingIndicator", 0, -textHeight * 0.5 * fraction, ColorAlpha(textColor, alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 4, ColorAlpha(shadowColor, alpha) ) cam.End3D2D() end end net.Receive("ixTypeClass", function() local client = net.ReadEntity() if (!IsValid(client) or client == LocalPlayer()) then return end local newClass = net.ReadString() local chatClass = ix.chat.classes[newClass] local text local range if (chatClass) then text = L(chatClass.indicator or "chatTyping") range = chatClass.range or math.pow(ix.config.Get("chatRange", 280), 2) elseif (newClass and newClass:sub(1, 1) == "@") then text = L(newClass:sub(2)) range = math.pow(ix.config.Get("chatRange", 280), 2) end if (ix.option.Get("disableAnimations", false)) then client.ixChatClassText = text client.ixChatClassRange = range else client.ixChatClassAnimation = tonumber(client.ixChatClassAnimation) or 0 if (text and !client.ixChatStarted) then client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 1}, "outCubic") client.ixChatClassText = text client.ixChatClassRange = range client.ixChatStarted = true elseif (!text and client.ixChatStarted) then client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 0}, "inCubic") client.ixChatStarted = nil end end end) else util.AddNetworkString("ixTypeClass") function PLUGIN:PlayerSpawn(client) net.Start("ixTypeClass") net.WriteEntity(client) net.WriteString("") net.Broadcast() end net.Receive("ixTypeClass", function(length, client) if ((client.ixNextTypeClass or 0) > RealTime()) then return end local newClass = net.ReadString() -- send message to players in pvs only since they're the only ones who can see the indicator -- we'll broadcast if the type class is empty because they might move out of pvs before the ending net message is sent net.Start("ixTypeClass") net.WriteEntity(client) net.WriteString(newClass) if (newClass == "") then net.Broadcast() else net.SendPVS(client:GetPos()) end client.ixNextTypeClass = RealTime() + 0.2 end) end ================================================ FILE: plugins/vendor/derma/cl_vendor.lua ================================================ local PANEL = {} AccessorFunc(PANEL, "bReadOnly", "ReadOnly", FORCE_BOOL) function PANEL:Init() self:SetSize(ScrW() * 0.45, ScrH() * 0.65) self:SetTitle("") self:MakePopup() self:Center() local header = self:Add("DPanel") header:SetTall(34) header:Dock(TOP) self.vendorName = header:Add("DLabel") self.vendorName:Dock(LEFT) self.vendorName:SetWide(self:GetWide() * 0.5 - 7) self.vendorName:SetText("John Doe") self.vendorName:SetTextInset(4, 0) self.vendorName:SetTextColor(color_white) self.vendorName:SetFont("ixMediumFont") self.ourName = header:Add("DLabel") self.ourName:Dock(RIGHT) self.ourName:SetWide(self:GetWide() * 0.5 - 7) self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")") self.ourName:SetTextInset(0, 0) self.ourName:SetTextColor(color_white) self.ourName:SetFont("ixMediumFont") local footer = self:Add("DPanel") footer:SetTall(34) footer:Dock(BOTTOM) footer:SetPaintBackground(false) self.vendorSell = footer:Add("DButton") self.vendorSell:SetFont("ixMediumFont") self.vendorSell:SetWide(self.vendorName:GetWide()) self.vendorSell:Dock(LEFT) self.vendorSell:SetContentAlignment(5) -- The text says purchase but the vendor is selling it to us. self.vendorSell:SetText(L"purchase") self.vendorSell:SetTextColor(color_white) self.vendorSell.DoClick = function(this) if (IsValid(self.activeSell)) then net.Start("ixVendorTrade") net.WriteString(self.activeSell.item) net.WriteBool(false) net.SendToServer() end end self.vendorBuy = footer:Add("DButton") self.vendorBuy:SetFont("ixMediumFont") self.vendorBuy:SetWide(self.ourName:GetWide()) self.vendorBuy:Dock(RIGHT) self.vendorBuy:SetContentAlignment(5) self.vendorBuy:SetText(L"sell") self.vendorBuy:SetTextColor(color_white) self.vendorBuy.DoClick = function(this) if (IsValid(self.activeBuy)) then net.Start("ixVendorTrade") net.WriteString(self.activeBuy.item) net.WriteBool(true) net.SendToServer() end end self.selling = self:Add("DScrollPanel") self.selling:SetWide(self:GetWide() * 0.5 - 7) self.selling:Dock(LEFT) self.selling:DockMargin(0, 4, 0, 4) self.selling:SetPaintBackground(true) self.sellingItems = self.selling:Add("DListLayout") self.sellingItems:SetSize(self.selling:GetSize()) self.sellingItems:DockPadding(0, 0, 0, 4) self.sellingItems:SetTall(ScrH()) self.buying = self:Add("DScrollPanel") self.buying:SetWide(self:GetWide() * 0.5 - 7) self.buying:Dock(RIGHT) self.buying:DockMargin(0, 4, 0, 4) self.buying:SetPaintBackground(true) self.buyingItems = self.buying:Add("DListLayout") self.buyingItems:SetSize(self.buying:GetSize()) self.buyingItems:DockPadding(0, 0, 0, 4) self.sellingList = {} self.buyingList = {} end function PANEL:addItem(uniqueID, listID) local entity = self.entity local items = entity.items local data = items[uniqueID] if ((!listID or listID == "selling") and !IsValid(self.sellingList[uniqueID]) and ix.item.list[uniqueID]) then if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_BUYONLY) then local item = self.sellingItems:Add("ixVendorItem") item:Setup(uniqueID) self.sellingList[uniqueID] = item self.sellingItems:InvalidateLayout() end end if ((!listID or listID == "buying") and !IsValid(self.buyingList[uniqueID]) and LocalPlayer():GetCharacter():GetInventory():HasItem(uniqueID)) then if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_SELLONLY) then local item = self.buyingItems:Add("ixVendorItem") item:Setup(uniqueID) item.isLocal = true self.buyingList[uniqueID] = item self.buyingItems:InvalidateLayout() end end end function PANEL:removeItem(uniqueID, listID) if (!listID or listID == "selling") then if (IsValid(self.sellingList[uniqueID])) then self.sellingList[uniqueID]:Remove() self.sellingItems:InvalidateLayout() end end if (!listID or listID == "buying") then if (IsValid(self.buyingList[uniqueID])) then self.buyingList[uniqueID]:Remove() self.buyingItems:InvalidateLayout() end end end function PANEL:Setup(entity) self.entity = entity self:SetTitle(entity:GetDisplayName()) self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..entity.money..")" or "")) self.vendorBuy:SetEnabled(!self:GetReadOnly()) self.vendorSell:SetEnabled(!self:GetReadOnly()) for k, _ in SortedPairs(entity.items) do self:addItem(k, "selling") end for _, v in SortedPairs(LocalPlayer():GetCharacter():GetInventory():GetItems()) do self:addItem(v.uniqueID, "buying") end end function PANEL:OnRemove() net.Start("ixVendorClose") net.SendToServer() if (IsValid(ix.gui.vendorEditor)) then ix.gui.vendorEditor:Remove() end end function PANEL:Think() local entity = self.entity if (!IsValid(entity)) then self:Remove() return end if ((self.nextUpdate or 0) < CurTime()) then self:SetTitle(self.entity:GetDisplayName()) self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..ix.currency.Get(entity.money)..")" or "")) self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")") self.nextUpdate = CurTime() + 0.25 end end function PANEL:OnItemSelected(panel) local price = self.entity:GetPrice(panel.item, panel.isLocal) if (panel.isLocal) then self.vendorBuy:SetText(L"sell".." ("..ix.currency.Get(price)..")") else self.vendorSell:SetText(L"purchase".." ("..ix.currency.Get(price)..")") end end vgui.Register("ixVendor", PANEL, "DFrame") PANEL = {} function PANEL:Init() self:SetTall(36) self:DockMargin(4, 4, 4, 0) self.icon = self:Add("SpawnIcon") self.icon:SetPos(2, 2) self.icon:SetSize(32, 32) self.icon:SetModel("models/error.mdl") self.name = self:Add("DLabel") self.name:Dock(FILL) self.name:DockMargin(42, 0, 0, 0) self.name:SetFont("ixChatFont") self.name:SetTextColor(color_white) self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200)) self.click = self:Add("DButton") self.click:Dock(FILL) self.click:SetText("") self.click.Paint = function() end self.click.DoClick = function(this) if (self.isLocal) then ix.gui.vendor.activeBuy = self else ix.gui.vendor.activeSell = self end ix.gui.vendor:OnItemSelected(self) end end function PANEL:SetCallback(callback) self.click.DoClick = function(this) callback() self.selected = true end end function PANEL:Setup(uniqueID) local item = ix.item.list[uniqueID] if (item) then self.item = uniqueID self.icon:SetModel(item:GetModel(), item:GetSkin()) self.name:SetText(item:GetName()) self.itemName = item:GetName() self.click:SetHelixTooltip(function(tooltip) ix.hud.PopulateItemTooltip(tooltip, item) local entity = ix.gui.vendor.entity if (entity and entity.items[self.item] and entity.items[self.item][VENDOR_MAXSTOCK]) then local info = entity.items[self.item] local stock = tooltip:AddRowAfter("name", "stock") stock:SetText(string.format("Stock: %d/%d", info[VENDOR_STOCK], info[VENDOR_MAXSTOCK])) stock:SetBackgroundColor(derma.GetColor("Info", self)) stock:SizeToContents() end end) end end function PANEL:Think() if ((self.nextUpdate or 0) < CurTime()) then local entity = ix.gui.vendor.entity if (entity and self.isLocal) then local count = LocalPlayer():GetCharacter():GetInventory():GetItemCount(self.item) if (count == 0) then self:Remove() end end self.nextUpdate = CurTime() + 0.1 end end function PANEL:Paint(w, h) if (ix.gui.vendor.activeBuy == self or ix.gui.vendor.activeSell == self) then surface.SetDrawColor(ix.config.Get("color")) else surface.SetDrawColor(0, 0, 0, 100) end surface.DrawRect(0, 0, w, h) end vgui.Register("ixVendorItem", PANEL, "DPanel") ================================================ FILE: plugins/vendor/derma/cl_vendoreditor.lua ================================================ local PANEL = {} function PANEL:Init() local entity = ix.gui.vendor.entity self:SetSize(320, 480) self:MoveLeftOf(ix.gui.vendor, 8) self:MakePopup() self:CenterVertical() self:SetTitle(L"vendorEditor") self.lblTitle:SetTextColor(color_white) self.name = self:Add("DTextEntry") self.name:Dock(TOP) self.name:SetText(entity:GetDisplayName()) self.name:SetPlaceholderText(L"name") self.name.OnEnter = function(this) if (entity:GetDisplayName() != this:GetText()) then self:updateVendor("name", this:GetText()) end end self.description = self:Add("DTextEntry") self.description:Dock(TOP) self.description:DockMargin(0, 4, 0, 0) self.description:SetText(entity:GetDescription()) self.description:SetPlaceholderText(L"description") self.description.OnEnter = function(this) if (entity:GetDescription() != this:GetText()) then self:updateVendor("description", this:GetText()) end end self.model = self:Add("DTextEntry") self.model:Dock(TOP) self.model:DockMargin(0, 4, 0, 0) self.model:SetText(entity:GetModel()) self.model:SetPlaceholderText(L"model") self.model.OnEnter = function(this) if (entity:GetModel():lower() != this:GetText():lower()) then self:updateVendor("model", this:GetText():lower()) end end local useMoney = tonumber(entity.money) != nil self.money = self:Add("DTextEntry") self.money:Dock(TOP) self.money:DockMargin(0, 4, 0, 0) self.money:SetText(!useMoney and "∞" or entity.money) self.money:SetPlaceholderText(L"money") self.money:SetDisabled(!useMoney) self.money:SetEnabled(useMoney) self.money:SetNumeric(true) self.money.OnEnter = function(this) local value = tonumber(this:GetText()) or entity.money if (value == entity.money) then return end self:updateVendor("money", value) end self.bubble = self:Add("DCheckBoxLabel") self.bubble:SetText(L"vendorNoBubble") self.bubble:Dock(TOP) self.bubble:DockMargin(0, 4, 0, 0) self.bubble:SetValue(entity:GetNoBubble() and 1 or 0) self.bubble.OnChange = function(this, value) if (this.noSend) then this.noSend = nil else self:updateVendor("bubble", value) end end self.useMoney = self:Add("DCheckBoxLabel") self.useMoney:SetText(L"vendorUseMoney") self.useMoney:Dock(TOP) self.useMoney:DockMargin(0, 4, 0, 0) self.useMoney:SetChecked(useMoney) self.useMoney.OnChange = function(this, value) self:updateVendor("useMoney") end self.sellScale = self:Add("DNumSlider") self.sellScale:Dock(TOP) self.sellScale:DockMargin(0, 4, 0, 0) self.sellScale:SetText(L"vendorSellScale") self.sellScale.Label:SetTextColor(color_white) self.sellScale.TextArea:SetTextColor(color_white) self.sellScale:SetDecimals(1) self.sellScale.noSend = true self.sellScale:SetValue(entity.scale) self.sellScale.OnValueChanged = function(this, value) if (this.noSend) then this.noSend = nil else timer.Create("ixVendorScale", 1, 1, function() if (IsValid(self) and IsValid(self.sellScale)) then value = self.sellScale:GetValue() if (value != entity.scale) then self:updateVendor("scale", value) end end end) end end self.faction = self:Add("DButton") self.faction:SetText(L"vendorFaction") self.faction:Dock(TOP) self.faction:SetTextColor(color_white) self.faction:DockMargin(0, 4, 0, 0) self.faction.DoClick = function(this) if (IsValid(ix.gui.editorFaction)) then ix.gui.editorFaction:Remove() end ix.gui.editorFaction = vgui.Create("ixVendorFactionEditor") ix.gui.editorFaction.updateVendor = self.updateVendor ix.gui.editorFaction.entity = entity ix.gui.editorFaction:Setup() end self.searchBar = self:Add("DTextEntry") self.searchBar:Dock(TOP) self.searchBar:DockMargin(0, 4, 0, 0) self.searchBar:SetUpdateOnType(true) self.searchBar:SetPlaceholderText("Search...") self.searchBar.OnValueChange = function(this, value) self:ReloadItemList(value) end local menu self.items = self:Add("DListView") self.items:Dock(FILL) self.items:DockMargin(0, 4, 0, 0) self.items:AddColumn(L"name").Header:SetTextColor(color_black) self.items:AddColumn(L"category").Header:SetTextColor(color_black) self.items:AddColumn(L"mode").Header:SetTextColor(color_black) self.items:AddColumn(L"price").Header:SetTextColor(color_black) self.items:AddColumn(L"stock").Header:SetTextColor(color_black) self.items:SetMultiSelect(false) self.items.OnRowRightClick = function(this, index, line) if (IsValid(menu)) then menu:Remove() end local uniqueID = line.item menu = DermaMenu() -- Modes of the item. local mode, panel = menu:AddSubMenu(L"mode") panel:SetImage("icon16/key.png") -- Disable buying/selling of the item. mode:AddOption(L"none", function() self:updateVendor("mode", {uniqueID, nil}) end):SetImage("icon16/cog_error.png") -- Allow the vendor to sell and buy this item. mode:AddOption(L"vendorBoth", function() self:updateVendor("mode", {uniqueID, VENDOR_SELLANDBUY}) end):SetImage("icon16/cog.png") -- Only allow the vendor to buy this item from players. mode:AddOption(L"vendorBuy", function() self:updateVendor("mode", {uniqueID, VENDOR_BUYONLY}) end):SetImage("icon16/cog_delete.png") -- Only allow the vendor to sell this item to players. mode:AddOption(L"vendorSell", function() self:updateVendor("mode", {uniqueID, VENDOR_SELLONLY}) end):SetImage("icon16/cog_add.png") local itemTable = ix.item.list[uniqueID] -- Set the price of the item. menu:AddOption(L"price", function() Derma_StringRequest( itemTable.GetName and itemTable:GetName() or L(itemTable.name), L"vendorPriceReq", entity:GetPrice(uniqueID), function(text) text = tonumber(text) if (text == itemTable.price) then text = nil end self:updateVendor("price", {uniqueID, text}) end ) end):SetImage("icon16/coins.png") -- Set the stock of the item or disable it. local stock, menuPanel = menu:AddSubMenu(L"stock") menuPanel:SetImage("icon16/table.png") -- Disable the use of stocks for this item. stock:AddOption(L"disable", function() self:updateVendor("stockDisable", uniqueID) end):SetImage("icon16/table_delete.png") -- Edit the maximum stock for this item. stock:AddOption(L"edit", function() local _, max = entity:GetStock(uniqueID) Derma_StringRequest( itemTable.GetName and itemTable:GetName() or L(itemTable.name), L"vendorStockReq", max or 1, function(text) self:updateVendor("stockMax", {uniqueID, text}) end ) end):SetImage("icon16/table_edit.png") -- Edit the current stock of this item. stock:AddOption(L"vendorEditCurStock", function() Derma_StringRequest( itemTable.GetName and itemTable:GetName() or L(itemTable.name), L"vendorStockCurReq", entity:GetStock(uniqueID) or 0, function(text) self:updateVendor("stock", {uniqueID, text}) end ) end):SetImage("icon16/table_edit.png") menu:Open() end self:ReloadItemList() end function PANEL:ReloadItemList(filter) local entity = ix.gui.vendor.entity self.lines = {} self.items:Clear() for k, v in SortedPairs(ix.item.list) do local itemName = v.GetName and v:GetName() or L(v.name) if (filter and !itemName:lower():find(filter:lower(), 1, false)) then continue end local mode = entity.items[k] and entity.items[k][VENDOR_MODE] local current, max = entity:GetStock(k) local panel = self.items:AddLine( itemName, v.category or L"none", mode and L(VENDOR_TEXT[mode]) or L"none", entity:GetPrice(k), max and current.."/"..max or "-" ) panel.item = k self.lines[k] = panel end end function PANEL:OnRemove() if (IsValid(ix.gui.vendor)) then ix.gui.vendor:Remove() end if (IsValid(ix.gui.editorFaction)) then ix.gui.editorFaction:Remove() end end function PANEL:updateVendor(key, value) net.Start("ixVendorEdit") net.WriteString(key) net.WriteType(value) net.SendToServer() end vgui.Register("ixVendorEditor", PANEL, "DFrame") ================================================ FILE: plugins/vendor/derma/cl_vendorfaction.lua ================================================ local PANEL = {} function PANEL:Init() self:SetSize(256, 280) self:Center() self:MakePopup() self:SetTitle(L"vendorFaction") self.scroll = self:Add("DScrollPanel") self.scroll:Dock(FILL) self.scroll:DockPadding(0, 0, 0, 4) self.factions = {} self.classes = {} for k, v in ipairs(ix.faction.indices) do local panel = self.scroll:Add("DPanel") panel:Dock(TOP) panel:DockPadding(4, 4, 4, 4) panel:DockMargin(0, 0, 0, 4) local faction = panel:Add("DCheckBoxLabel") faction:Dock(TOP) faction:SetText(L(v.name)) faction:DockMargin(0, 0, 0, 4) faction.OnChange = function(this, state) self:updateVendor("faction", v.uniqueID) end self.factions[v.uniqueID] = faction for _, v2 in ipairs(ix.class.list) do if (v2.faction == k) then local class = panel:Add("DCheckBoxLabel") class:Dock(TOP) class:DockMargin(16, 0, 0, 4) class:SetText(L(v2.name)) class.OnChange = function(this, state) self:updateVendor("class", v2.uniqueID) end self.classes[v2.uniqueID] = class panel:SetTall(panel:GetTall() + class:GetTall() + 4) end end end end function PANEL:Setup() for k, _ in pairs(self.entity.factions or {}) do self.factions[k]:SetChecked(true) end for k, _ in pairs(self.entity.classes or {}) do self.classes[k]:SetChecked(true) end end vgui.Register("ixVendorFactionEditor", PANEL, "DFrame") ================================================ FILE: plugins/vendor/entities/entities/ix_vendor.lua ================================================ ENT.Type = "anim" ENT.PrintName = "Vendor" ENT.Category = "Helix" ENT.Spawnable = true ENT.AdminOnly = true ENT.isVendor = true ENT.bNoPersist = true function ENT:SetupDataTables() self:NetworkVar("Bool", 0, "NoBubble") self:NetworkVar("String", 0, "DisplayName") self:NetworkVar("String", 1, "Description") end function ENT:Initialize() if (SERVER) then self:SetModel("models/mossman.mdl") self:SetUseType(SIMPLE_USE) self:SetMoveType(MOVETYPE_NONE) self:DrawShadow(true) self:InitPhysObj() self:AddCallback("OnAngleChange", function(entity) local mins, maxs = entity:GetAxisAlignedBoundingBox() entity:SetCollisionBounds(mins, maxs) end) self.items = {} self.messages = {} self.factions = {} self.classes = {} self:SetDisplayName("John Doe") self:SetDescription("") self.receivers = {} end timer.Simple(1, function() if (IsValid(self)) then self:SetAnim() end end) end function ENT:InitPhysObj() local mins, maxs = self:GetAxisAlignedBoundingBox() local bPhysObjCreated = self:PhysicsInitBox(mins, maxs) if (bPhysObjCreated) then local physObj = self:GetPhysicsObject() physObj:EnableMotion(false) physObj:Sleep() end end function ENT:GetAxisAlignedBoundingBox() local mins, maxs = self:GetModelBounds() mins = Vector(mins.x, mins.y, 0) mins, maxs = self:GetRotatedAABB(mins, maxs) return mins, maxs end function ENT:CanAccess(client) local bAccess = false local uniqueID = ix.faction.indices[client:Team()].uniqueID if (self.factions and !table.IsEmpty(self.factions)) then if (self.factions[uniqueID]) then bAccess = true else return false end end if (bAccess and self.classes and !table.IsEmpty(self.classes)) then local class = ix.class.list[client:GetCharacter():GetClass()] local classID = class and class.uniqueID if (classID and !self.classes[classID]) then return false end end return true end function ENT:GetStock(uniqueID) if (self.items[uniqueID] and self.items[uniqueID][VENDOR_MAXSTOCK]) then return self.items[uniqueID][VENDOR_STOCK] or 0, self.items[uniqueID][VENDOR_MAXSTOCK] end end function ENT:GetPrice(uniqueID, selling) local price = ix.item.list[uniqueID] and self.items[uniqueID] and self.items[uniqueID][VENDOR_PRICE] or ix.item.list[uniqueID].price or 0 if (selling) then price = math.floor(price * (self.scale or 0.5)) end return price end function ENT:CanSellToPlayer(client, uniqueID) local data = self.items[uniqueID] if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then return false end if (data[VENDOR_MODE] == VENDOR_BUYONLY) then return false end if (!client:GetCharacter():HasMoney(self:GetPrice(uniqueID))) then return false end if (data[VENDOR_STOCK] and data[VENDOR_STOCK] < 1) then return false end return true end function ENT:CanBuyFromPlayer(client, uniqueID) local data = self.items[uniqueID] if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then return false end if (data[VENDOR_MODE] != VENDOR_SELLONLY) then return false end if (!self:HasMoney(data[VENDOR_PRICE] or ix.item.list[uniqueID].price or 0)) then return false end return true end function ENT:HasMoney(amount) -- Vendor not using money system so they can always afford it. if (!self.money) then return true end return self.money >= amount end function ENT:SetAnim() for k, v in ipairs(self:GetSequenceList()) do if (v:lower():find("idle") and v != "idlenoise") then return self:ResetSequence(k) end end if (self:GetSequenceCount() > 1) then self:ResetSequence(4) end end if (SERVER) then local PLUGIN = PLUGIN function ENT:SpawnFunction(client, trace) local angles = (trace.HitPos - client:GetPos()):Angle() angles.r = 0 angles.p = 0 angles.y = angles.y + 180 local entity = ents.Create("ix_vendor") entity:SetPos(trace.HitPos) entity:SetAngles(angles) entity:Spawn() PLUGIN:SaveData() return entity end function ENT:Use(activator) local character = activator:GetCharacter() if (!self:CanAccess(activator) or hook.Run("CanPlayerUseVendor", activator, self) == false) then if (self.messages[VENDOR_NOTRADE]) then activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_NOTRADE]) else activator:NotifyLocalized("vendorNoTrade") end return end self.receivers[#self.receivers + 1] = activator if (self.messages[VENDOR_WELCOME]) then activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_WELCOME]) end local items = {} -- Only send what is needed. for k, v in pairs(self.items) do if (!table.IsEmpty(v) and (CAMI.PlayerHasAccess(activator, "Helix - Manage Vendors", nil) or v[VENDOR_MODE])) then items[k] = v end end self.scale = self.scale or 0.5 activator.ixVendor = self -- force sync to prevent outdated inventories while buying/selling if (character) then character:GetInventory():Sync(activator, true) end net.Start("ixVendorOpen") net.WriteEntity(self) net.WriteUInt(self.money or 0, 16) net.WriteTable(items) net.WriteFloat(self.scale or 0.5) net.Send(activator) ix.log.Add(activator, "vendorUse", self:GetDisplayName()) end function ENT:SetMoney(value) self.money = value net.Start("ixVendorMoney") net.WriteUInt(value and value or -1, 16) net.Send(self.receivers) end function ENT:GiveMoney(value) if (self.money) then self:SetMoney(self:GetMoney() + value) end end function ENT:TakeMoney(value) if (self.money) then self:GiveMoney(-value) end end function ENT:SetStock(uniqueID, value) if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then return end self.items[uniqueID] = self.items[uniqueID] or {} self.items[uniqueID][VENDOR_STOCK] = math.min(value, self.items[uniqueID][VENDOR_MAXSTOCK]) net.Start("ixVendorStock") net.WriteString(uniqueID) net.WriteUInt(value, 16) net.Send(self.receivers) end function ENT:AddStock(uniqueID, value) if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then return end self:SetStock(uniqueID, self:GetStock(uniqueID) + (value or 1)) end function ENT:TakeStock(uniqueID, value) if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then return end self:AddStock(uniqueID, -(value or 1)) end else function ENT:CreateBubble() self.bubble = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE) self.bubble:SetPos(self:GetPos() + Vector(0, 0, 84)) self.bubble:SetModelScale(0.6, 0) end function ENT:Draw() local bubble = self.bubble if (IsValid(bubble)) then local realTime = RealTime() bubble:SetRenderOrigin(self:GetPos() + Vector(0, 0, 84 + math.sin(realTime * 3) * 0.05)) bubble:SetRenderAngles(Angle(0, realTime * 100, 0)) end self:DrawModel() end function ENT:Think() local noBubble = self:GetNoBubble() if (IsValid(self.bubble) and noBubble) then self.bubble:Remove() elseif (!IsValid(self.bubble) and !noBubble) then self:CreateBubble() end if ((self.nextAnimCheck or 0) < CurTime()) then self:SetAnim() self.nextAnimCheck = CurTime() + 60 end self:SetNextClientThink(CurTime() + 0.25) return true end function ENT:OnRemove() if (IsValid(self.bubble)) then self.bubble:Remove() end end ENT.PopulateEntityInfo = true function ENT:OnPopulateEntityInfo(container) local name = container:AddRow("name") name:SetImportant() name:SetText(self:GetDisplayName()) name:SizeToContents() local descriptionText = self:GetDescription() if (descriptionText != "") then local description = container:AddRow("description") description:SetText(self:GetDescription()) description:SizeToContents() end end end function ENT:GetMoney() return self.money end ================================================ FILE: plugins/vendor/sh_plugin.lua ================================================ -- luacheck: globals VENDOR_BUY VENDOR_SELL VENDOR_BOTH VENDOR_WELCOME VENDOR_LEAVE VENDOR_NOTRADE VENDOR_PRICE -- luacheck: globals VENDOR_STOCK VENDOR_MODE VENDOR_MAXSTOCK VENDOR_SELLANDBUY VENDOR_SELLONLY VENDOR_BUYONLY VENDOR_TEXT local PLUGIN = PLUGIN PLUGIN.name = "Vendors" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds NPC vendors that can sell things." CAMI.RegisterPrivilege({ Name = "Helix - Manage Vendors", MinAccess = "admin" }) VENDOR_BUY = 1 VENDOR_SELL = 2 VENDOR_BOTH = 3 -- Keys for vendor messages. VENDOR_WELCOME = 1 VENDOR_LEAVE = 2 VENDOR_NOTRADE = 3 -- Keys for item information. VENDOR_PRICE = 1 VENDOR_STOCK = 2 VENDOR_MODE = 3 VENDOR_MAXSTOCK = 4 -- Sell and buy the item. VENDOR_SELLANDBUY = 1 -- Only sell the item to the player. VENDOR_SELLONLY = 2 -- Only buy the item from the player. VENDOR_BUYONLY = 3 if (SERVER) then util.AddNetworkString("ixVendorOpen") util.AddNetworkString("ixVendorClose") util.AddNetworkString("ixVendorTrade") util.AddNetworkString("ixVendorEdit") util.AddNetworkString("ixVendorEditFinish") util.AddNetworkString("ixVendorEditor") util.AddNetworkString("ixVendorMoney") util.AddNetworkString("ixVendorStock") util.AddNetworkString("ixVendorAddItem") function PLUGIN:SaveData() local data = {} for _, entity in ipairs(ents.FindByClass("ix_vendor")) do local bodygroups = {} for _, v in ipairs(entity:GetBodyGroups() or {}) do bodygroups[v.id] = entity:GetBodygroup(v.id) end data[#data + 1] = { name = entity:GetDisplayName(), description = entity:GetDescription(), pos = entity:GetPos(), angles = entity:GetAngles(), model = entity:GetModel(), skin = entity:GetSkin(), bodygroups = bodygroups, bubble = entity:GetNoBubble(), items = entity.items, factions = entity.factions, classes = entity.classes, money = entity.money, scale = entity.scale } end self:SetData(data) end function PLUGIN:LoadData() for _, v in ipairs(self:GetData() or {}) do local entity = ents.Create("ix_vendor") entity:SetPos(v.pos) entity:SetAngles(v.angles) entity:Spawn() entity:SetModel(v.model) entity:SetSkin(v.skin or 0) entity:InitPhysObj() entity:SetNoBubble(v.bubble) entity:SetDisplayName(v.name) entity:SetDescription(v.description) for id, bodygroup in pairs(v.bodygroups or {}) do entity:SetBodygroup(id, bodygroup) end local items = {} for uniqueID, data in pairs(v.items) do items[tostring(uniqueID)] = data end entity.items = items entity.factions = v.factions or {} entity.classes = v.classes or {} entity.money = v.money entity.scale = v.scale or 0.5 end end function PLUGIN:CanVendorSellItem(client, vendor, itemID) local tradeData = vendor.items[itemID] local char = client:GetCharacter() if (!tradeData or !char) then return false end if (!char:HasMoney(tradeData[1] or 0)) then return false end return true end ix.log.AddType("vendorUse", function(client, ...) local arg = {...} return string.format("%s used the '%s' vendor.", client:Name(), arg[1]) end) ix.log.AddType("vendorBuy", function(client, ...) local arg = {...} return string.format("%s purchased a '%s' from the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3]) end) ix.log.AddType("vendorSell", function(client, ...) local arg = {...} return string.format("%s sold a '%s' to the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3]) end) net.Receive("ixVendorClose", function(length, client) local entity = client.ixVendor if (IsValid(entity)) then for k, v in ipairs(entity.receivers) do if (v == client) then table.remove(entity.receivers, k) break end end client.ixVendor = nil end end) local function UpdateEditReceivers(receivers, key, value) net.Start("ixVendorEdit") net.WriteString(key) net.WriteType(value) net.Send(receivers) end net.Receive("ixVendorEdit", function(length, client) if (!CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)) then return end local entity = client.ixVendor if (!IsValid(entity)) then return end local key = net.ReadString() local data = net.ReadType() local feedback = true if (key == "name") then entity:SetDisplayName(data) elseif (key == "description") then entity:SetDescription(data) elseif (key == "bubble") then entity:SetNoBubble(data) elseif (key == "mode") then local uniqueID = data[1] entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_MODE] = data[2] UpdateEditReceivers(entity.receivers, key, data) elseif (key == "price") then local uniqueID = data[1] data[2] = tonumber(data[2]) if (data[2]) then data[2] = math.Round(data[2]) end entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_PRICE] = data[2] UpdateEditReceivers(entity.receivers, key, data) data = uniqueID elseif (key == "stockDisable") then local uniqueID = data[1] entity.items[data] = entity.items[uniqueID] or {} entity.items[data][VENDOR_MAXSTOCK] = nil UpdateEditReceivers(entity.receivers, key, data) elseif (key == "stockMax") then local uniqueID = data[1] data[2] = math.max(math.Round(tonumber(data[2]) or 1), 1) entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2] entity.items[uniqueID][VENDOR_STOCK] = math.Clamp(entity.items[uniqueID][VENDOR_STOCK] or data[2], 1, data[2]) data[3] = entity.items[uniqueID][VENDOR_STOCK] UpdateEditReceivers(entity.receivers, key, data) data = uniqueID elseif (key == "stock") then local uniqueID = data[1] entity.items[uniqueID] = entity.items[uniqueID] or {} if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then data[2] = math.max(math.Round(tonumber(data[2]) or 0), 0) entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2] end data[2] = math.Clamp(math.Round(tonumber(data[2]) or 0), 0, entity.items[uniqueID][VENDOR_MAXSTOCK]) entity.items[uniqueID][VENDOR_STOCK] = data[2] UpdateEditReceivers(entity.receivers, key, data) data = uniqueID elseif (key == "faction") then local faction = ix.faction.teams[data] if (faction) then entity.factions[data] = !entity.factions[data] if (!entity.factions[data]) then entity.factions[data] = nil end end local uniqueID = data data = {uniqueID, entity.factions[uniqueID]} elseif (key == "class") then local class for _, v in ipairs(ix.class.list) do if (v.uniqueID == data) then class = v break end end if (class) then entity.classes[data] = !entity.classes[data] if (!entity.classes[data]) then entity.classes[data] = nil end end local uniqueID = data data = {uniqueID, entity.classes[uniqueID]} elseif (key == "model") then entity:SetModel(data) entity:InitPhysObj() entity:SetAnim() elseif (key == "useMoney") then if (entity.money) then entity:SetMoney() else entity:SetMoney(0) end elseif (key == "money") then data = math.Round(math.abs(tonumber(data) or 0)) entity:SetMoney(data) feedback = false elseif (key == "scale") then data = tonumber(data) or 0.5 entity.scale = data UpdateEditReceivers(entity.receivers, key, data) end PLUGIN:SaveData() if (feedback) then local receivers = {} for _, v in ipairs(entity.receivers) do if (CAMI.PlayerHasAccess(v, "Helix - Manage Vendors", nil)) then receivers[#receivers + 1] = v end end net.Start("ixVendorEditFinish") net.WriteString(key) net.WriteType(data) net.Send(receivers) end end) net.Receive("ixVendorTrade", function(length, client) if ((client.ixVendorTry or 0) < CurTime()) then client.ixVendorTry = CurTime() + 0.33 else return end local entity = client.ixVendor if (!IsValid(entity) or client:GetPos():Distance(entity:GetPos()) > 192) then return end if (!entity:CanAccess(client)) then return end local uniqueID = net.ReadString() local isSellingToVendor = net.ReadBool() if (entity.items[uniqueID] and hook.Run("CanPlayerTradeWithVendor", client, entity, uniqueID, isSellingToVendor) != false) then local price = entity:GetPrice(uniqueID, isSellingToVendor) if (isSellingToVendor) then local found = false local name if (!entity:HasMoney(price)) then return client:NotifyLocalized("vendorNoMoney") end local stock, max = entity:GetStock(uniqueID) if (stock and stock >= max) then return client:NotifyLocalized("vendorMaxStock") end local invOkay = true for k, _ in client:GetCharacter():GetInventory():Iter() do if (k.uniqueID == uniqueID and k:GetID() != 0 and ix.item.instances[k:GetID()] and k:GetData("equip", false) == false) then invOkay = k:Remove() found = true name = L(k.name, client) break end end if (!found) then return end if (!invOkay) then client:GetCharacter():GetInventory():Sync(client, true) return client:NotifyLocalized("tellAdmin", "trd!iid") end client:GetCharacter():GiveMoney(price, price == 0) client:NotifyLocalized("businessSell", name, ix.currency.Get(price)) entity:TakeMoney(price) entity:AddStock(uniqueID) ix.log.Add(client, "vendorSell", name, entity:GetDisplayName(), ix.currency.Get(price)) else local stock = entity:GetStock(uniqueID) if (stock and stock < 1) then return client:NotifyLocalized("vendorNoStock") end if (!client:GetCharacter():HasMoney(price)) then return client:NotifyLocalized("canNotAfford") end if !entity:CanSellToPlayer(client, uniqueID) then return false end local name = L(ix.item.list[uniqueID].name, client) client:GetCharacter():TakeMoney(price, price == 0) client:NotifyLocalized("businessPurchase", name, ix.currency.Get(price)) entity:GiveMoney(price) if (!client:GetCharacter():GetInventory():Add(uniqueID)) then ix.item.Spawn(uniqueID, client) else net.Start("ixVendorAddItem") net.WriteString(uniqueID) net.Send(client) end entity:TakeStock(uniqueID) ix.log.Add(client, "vendorBuy", name, entity:GetDisplayName(), ix.currency.Get(price)) end PLUGIN:SaveData() hook.Run("CharacterVendorTraded", client, entity, uniqueID, isSellingToVendor) else client:NotifyLocalized("vendorNoTrade") end end) else VENDOR_TEXT = {} VENDOR_TEXT[VENDOR_SELLANDBUY] = "vendorBoth" VENDOR_TEXT[VENDOR_BUYONLY] = "vendorBuy" VENDOR_TEXT[VENDOR_SELLONLY] = "vendorSell" net.Receive("ixVendorOpen", function() local entity = net.ReadEntity() if (!IsValid(entity)) then return end entity.money = net.ReadUInt(16) entity.items = net.ReadTable() entity.scale = net.ReadFloat() ix.gui.vendor = vgui.Create("ixVendor") ix.gui.vendor:SetReadOnly(false) ix.gui.vendor:Setup(entity) end) net.Receive("ixVendorEditor", function() local entity = net.ReadEntity() if (!IsValid(entity) or !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Vendors", nil)) then return end entity.money = net.ReadUInt(16) entity.items = net.ReadTable() entity.scale = net.ReadFloat() entity.messages = net.ReadTable() entity.factions = net.ReadTable() entity.classes = net.ReadTable() ix.gui.vendor = vgui.Create("ixVendor") ix.gui.vendor:SetReadOnly(true) ix.gui.vendor:Setup(entity) ix.gui.vendorEditor = vgui.Create("ixVendorEditor") end) net.Receive("ixVendorEdit", function() local panel = ix.gui.vendor if (!IsValid(panel)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local key = net.ReadString() local data = net.ReadType() if (key == "mode") then entity.items[data[1]] = entity.items[data[1]] or {} entity.items[data[1]][VENDOR_MODE] = data[2] if (!data[2]) then panel:removeItem(data[1]) elseif (data[2] == VENDOR_SELLANDBUY) then panel:addItem(data[1]) else panel:addItem(data[1], data[2] == VENDOR_SELLONLY and "selling" or "buying") panel:removeItem(data[1], data[2] == VENDOR_SELLONLY and "buying" or "selling") end elseif (key == "price") then local uniqueID = data[1] entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_PRICE] = tonumber(data[2]) elseif (key == "stockDisable") then if (entity.items[data]) then entity.items[data][VENDOR_MAXSTOCK] = nil end elseif (key == "stockMax") then local uniqueID = data[1] local value = data[2] local current = data[3] entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_MAXSTOCK] = value entity.items[uniqueID][VENDOR_STOCK] = current elseif (key == "stock") then local uniqueID = data[1] local value = data[2] entity.items[uniqueID] = entity.items[uniqueID] or {} if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then entity.items[uniqueID][VENDOR_MAXSTOCK] = value end entity.items[uniqueID][VENDOR_STOCK] = value elseif (key == "scale") then entity.scale = data end end) net.Receive("ixVendorEditFinish", function() local panel = ix.gui.vendor local editor = ix.gui.vendorEditor if (!IsValid(panel) or !IsValid(editor)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local key = net.ReadString() local data = net.ReadType() if (key == "name") then editor.name:SetText(data) elseif (key == "description") then editor.description:SetText(data) elseif (key == "bubble") then editor.bubble.noSend = true editor.bubble:SetValue(data and 1 or 0) elseif (key == "mode") then if (data[2] == nil) then editor.lines[data[1]]:SetValue(3, L"none") else editor.lines[data[1]]:SetValue(3, L(VENDOR_TEXT[data[2]])) end elseif (key == "price") then editor.lines[data]:SetValue(4, entity:GetPrice(data)) elseif (key == "stockDisable") then editor.lines[data]:SetValue(5, "-") elseif (key == "stockMax" or key == "stock") then local current, max = entity:GetStock(data) editor.lines[data]:SetValue(5, current.."/"..max) elseif (key == "faction") then local uniqueID = data[1] local state = data[2] local editPanel = ix.gui.editorFaction entity.factions[uniqueID] = state if (IsValid(editPanel) and IsValid(editPanel.factions[uniqueID])) then editPanel.factions[uniqueID]:SetChecked(state == true) end elseif (key == "class") then local uniqueID = data[1] local state = data[2] local editPanel = ix.gui.editorFaction entity.classes[uniqueID] = state if (IsValid(editPanel) and IsValid(editPanel.classes[uniqueID])) then editPanel.classes[uniqueID]:SetChecked(state == true) end elseif (key == "model") then editor.model:SetText(entity:GetModel()) elseif (key == "scale") then editor.sellScale.noSend = true editor.sellScale:SetValue(data) end surface.PlaySound("buttons/button14.wav") end) net.Receive("ixVendorMoney", function() local panel = ix.gui.vendor if (!IsValid(panel)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local value = net.ReadUInt(16) value = value != -1 and value or nil entity.money = value local editor = ix.gui.vendorEditor if (IsValid(editor)) then local useMoney = tonumber(value) != nil editor.money:SetDisabled(!useMoney) editor.money:SetEnabled(useMoney) editor.money:SetText(useMoney and value or "∞") end end) net.Receive("ixVendorStock", function() local panel = ix.gui.vendor if (!IsValid(panel)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local uniqueID = net.ReadString() local amount = net.ReadUInt(16) entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_STOCK] = amount local editor = ix.gui.vendorEditor if (IsValid(editor)) then local _, max = entity:GetStock(uniqueID) editor.lines[uniqueID]:SetValue(4, amount .. "/" .. max) end end) net.Receive("ixVendorAddItem", function() local uniqueID = net.ReadString() if (IsValid(ix.gui.vendor)) then ix.gui.vendor:addItem(uniqueID, "buying") end end) end properties.Add("vendor_edit", { MenuLabel = "Edit Vendor", Order = 999, MenuIcon = "icon16/user_edit.png", Filter = function(self, entity, client) if (!IsValid(entity)) then return false end if (entity:GetClass() != "ix_vendor") then return false end if (!gamemode.Call( "CanProperty", client, "vendor_edit", entity)) then return false end return CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil) end, Action = function(self, entity) self:MsgStart() net.WriteEntity(entity) self:MsgEnd() end, Receive = function(self, length, client) local entity = net.ReadEntity() if (!IsValid(entity)) then return end if (!self:Filter(entity, client)) then return end entity.receivers[#entity.receivers + 1] = client local itemsTable = {} for k, v in pairs(entity.items) do if (!table.IsEmpty(v)) then itemsTable[k] = v end end client.ixVendor = entity net.Start("ixVendorEditor") net.WriteEntity(entity) net.WriteUInt(entity.money or 0, 16) net.WriteTable(itemsTable) net.WriteFloat(entity.scale or 0.5) net.WriteTable(entity.messages) net.WriteTable(entity.factions) net.WriteTable(entity.classes) net.Send(client) end }) ================================================ FILE: plugins/wepselect.lua ================================================ PLUGIN.name = "Weapon Select" PLUGIN.author = "Chessnut" PLUGIN.description = "A replacement for the default weapon selection." if (CLIENT) then PLUGIN.index = PLUGIN.index or 1 PLUGIN.deltaIndex = PLUGIN.deltaIndex or PLUGIN.index PLUGIN.infoAlpha = PLUGIN.infoAlpha or 0 PLUGIN.alpha = PLUGIN.alpha or 0 PLUGIN.alphaDelta = PLUGIN.alphaDelta or PLUGIN.alpha PLUGIN.fadeTime = PLUGIN.fadeTime or 0 local matrixScale = Vector(1, 1, 0) function PLUGIN:LoadFonts(font, genericFont) surface.CreateFont("ixWeaponSelectFont", { font = font, size = ScreenScale(16), extended = true, weight = 1000 }) end function PLUGIN:HUDShouldDraw(name) if (name == "CHudWeaponSelection") then return false end end function PLUGIN:HUDPaint() local frameTime = FrameTime() self.alphaDelta = Lerp(frameTime * 10, self.alphaDelta, self.alpha) local fraction = self.alphaDelta if (fraction > 0.01) then local x, y = ScrW() * 0.5, ScrH() * 0.5 local spacing = math.pi * 0.85 local radius = 240 * self.alphaDelta local shiftX = ScrW() * .02 self.deltaIndex = Lerp(frameTime * 12, self.deltaIndex, self.index) local weapons = LocalPlayer():GetWeapons() local index = self.deltaIndex if (!weapons[self.index]) then self.index = #weapons end for i = 1, #weapons do local theta = (i - index) * 0.1 local color = ColorAlpha( i == self.index and ix.config.Get("color") or color_white, (255 - math.abs(theta * 3) * 255) * fraction ) local lastY = 0 if (self.markup and (i < self.index or i == 1)) then if (self.index != 1) then local _, h = self.markup:Size() lastY = h * fraction end if (i == 1 or i == self.index - 1) then self.infoAlpha = Lerp(frameTime * 3, self.infoAlpha, 255) self.markup:Draw(x + 6 + shiftX, y + 30, 0, 0, self.infoAlpha * fraction) end end surface.SetFont("ixWeaponSelectFont") local weaponName = language.GetPhrase(weapons[i]:GetPrintName()):utf8upper() local _, ty = surface.GetTextSize(weaponName) local scale = 1 - math.abs(theta * 2) local matrix = Matrix() matrix:Translate(Vector( shiftX + x + math.cos(theta * spacing + math.pi) * radius + radius, y + lastY + math.sin(theta * spacing + math.pi) * radius - ty / 2 , 1)) matrix:Scale(matrixScale * scale) cam.PushModelMatrix(matrix) ix.util.DrawText(weaponName, 2, ty / 2, color, 0, 1, "ixWeaponSelectFont") cam.PopModelMatrix() end if (self.fadeTime < CurTime() and self.alpha > 0) then self.alpha = 0 end end end function PLUGIN:OnIndexChanged(weapon) self.alpha = 1 self.fadeTime = CurTime() + 5 self.markup = nil if (IsValid(weapon)) then local instructions = weapon.Instructions local text = "" if (instructions != nil and instructions:find("%S")) then local color = ix.config.Get("color") text = text .. string.format( "%s\n%s\n", color.r, color.g, color.b, L("Instructions"), instructions ) end if (text != "") then self.markup = markup.Parse(""..text, ScrW() * 0.3) self.infoAlpha = 0 end local source, pitch = hook.Run("WeaponCycleSound") LocalPlayer():EmitSound(source or "common/talk.wav", 50, pitch or 180) end end function PLUGIN:PlayerBindPress(client, bind, pressed) bind = bind:lower() if (!pressed or !bind:find("invprev") and !bind:find("invnext") and !bind:find("slot") and !bind:find("attack")) then return end local currentWeapon = client:GetActiveWeapon() local bValid = IsValid(currentWeapon) local bTool if (client:InVehicle() or (bValid and currentWeapon:GetClass() == "weapon_physgun" and client:KeyDown(IN_ATTACK))) then return end if (bValid and currentWeapon:GetClass() == "gmod_tool") then local tool = client:GetTool() bTool = tool and (tool.Scroll != nil) end local weapons = client:GetWeapons() if (bind:find("invprev") and !bTool) then local oldIndex = self.index self.index = math.min(self.index + 1, #weapons) if (self.alpha == 0 or oldIndex != self.index) then self:OnIndexChanged(weapons[self.index]) end return true elseif (bind:find("invnext") and !bTool) then local oldIndex = self.index self.index = math.max(self.index - 1, 1) if (self.alpha == 0 or oldIndex != self.index) then self:OnIndexChanged(weapons[self.index]) end return true elseif (bind:find("slot")) then self.index = math.Clamp(tonumber(bind:match("slot(%d)")) or 1, 1, #weapons) self:OnIndexChanged(weapons[self.index]) return true elseif (bind:find("attack") and self.alpha > 0) then local weapon = weapons[self.index] if (IsValid(weapon)) then LocalPlayer():EmitSound(hook.Run("WeaponSelectSound", weapon) or "HL2Player.Use") input.SelectWeapon(weapon) self.alpha = 0 end return true end end function PLUGIN:Think() local client = LocalPlayer() if (!IsValid(client) or !client:Alive()) then self.alpha = 0 end end function PLUGIN:ScoreboardShow() self.alpha = 0 end function PLUGIN:ShouldPopulateEntityInfo(entity) if (self.alpha > 0) then return false end end end