diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d950186 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ryhon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ModLoader.gd b/ModLoader.gd new file mode 100644 index 0000000..cfd9b57 --- /dev/null +++ b/ModLoader.gd @@ -0,0 +1,16 @@ +extends SceneTree + +func _initialize(): + var modsDir = OS.get_executable_path().get_base_dir() + "/mods" + print("Loading mods from ", modsDir) + var da = DirAccess.open(modsDir) + da.list_dir_begin() + var pck = da.get_next() + while pck: + print("Loading ", pck) + ProjectSettings.load_resource_pack(modsDir + "/" + pck) + pck = da.get_next() + print("Done") + + # Change scene to main scene + change_scene_to_packed(load(ProjectSettings.get_setting_with_override("application/run/main_scene"))) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5312769 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Vostok Mods +This is an experimental mod loader for Road to Vostok. +RTV doesn't natively support mods, so we need to inject a script that loads other PCKs and runs other scripts. +The mod loader is injected by appending a script to the game PCK using [GodotPckTool](https://github.com/hhyyrylainen/GodotPckTool) and using the `--script` option. +Mod PCKs (or ZIPs) will overwrite existing game files at runtime. Partial patches are not supported. + +## Installation +Press the green "Code" button at the top of the page and press "Download ZIP". +Extract the contents of the installation of your game so that the `mods` directory is in the same folder as the game executable. +Download [GodotPckTool](https://github.com/hhyyrylainen/GodotPckTool/releases) and put it in your game's directory. +To run the game with mods, run the `run.sh` script in the game directory. Windows is not supported yet. + +## Creating mods +Download [GodotPckTool](https://github.com/hhyyrylainen/GodotPckTool) to extract the game PCK. +All the extracted resources will be in a binary form, you can use [Godot RE Tools](https://github.com/bruvzg/gdsdecomp) to convert them back to usable formats. +Decompiling the GDScript scripts requires [v0.7.0-prerelease.1](https://github.com/bruvzg/gdsdecomp/releases/tag/v0.7.0-prerelease.1) or newer. +To create a mod, create a new folder in the `mod_src` directory. +To modify a script or asset, extract it and decompile it. +If a script has a `class_name`, it needs to be removed. +Files must be in the same relative directory to your mod folder as in the PCK (e.g. `res://Scripts/Character.gd` -> `mod_src/MyMod/Character.gd`). + +### .remap files +This process may be automated in the future by the build script. + +A lot of assets will have a `.remap` file, which acts like a symlink. `.remap` files have the following format: +``` +[remap] +path="res://path/to/target.file" +``` +Mod PCKs can overwrite the `.remap` files. For example, if you want to overwrite the `res://Scripts/Character.gd` file (which is remapped to `res://Scripts/Character.gdc`), you need to create a `Scripts/Character.gd.remap` file, which remaps to another file, e.g. `Scripts/Character.mod.gd`. + +## Sample mods +These mods are included in the `mod_src` directory +### FPS++ +Disables 4xMSAA in performance mode, FSR 2.2 is enabled and resolution scale can be adjusted with up/down arrow keys. +## SimpleControls +Simplifies the controls. +Weapon is always ready when not sprinting, arm stamina is only consumed while aiming. Bolt- and pump- action weapons automatically chamber next round and reload automatically with a single reload button press. \ No newline at end of file diff --git a/mod_src/FPS++/Scripts/Camera.gd.remap b/mod_src/FPS++/Scripts/Camera.gd.remap new file mode 100644 index 0000000..23bcaac --- /dev/null +++ b/mod_src/FPS++/Scripts/Camera.gd.remap @@ -0,0 +1,2 @@ +[remap] +path="res://Scripts/Camera.mod.gd" diff --git a/mod_src/FPS++/Scripts/Camera.mod.gd b/mod_src/FPS++/Scripts/Camera.mod.gd new file mode 100644 index 0000000..e4562e9 --- /dev/null +++ b/mod_src/FPS++/Scripts/Camera.mod.gd @@ -0,0 +1,143 @@ +extends Camera3D + +var gameData = preload("res://Resources/GameData.tres") + +@export var camera: Camera3D +@export var attribute: CameraAttributesPractical +@onready var weapons = $Weapons + +var translateSpeed: = 4.0 +var rotateSpeed: = 4.0 +var nearFarSpeed: = 1.0 +var FOVSpeed: = 1.0 +var interpolate = false +var currentRID: RID + +static var fsr_scale = 1.0 +static var fpspp_initialized : bool = false + +func _ready() -> void: + if !fpspp_initialized: + InputMap.add_action("fsr_up") + var up = InputEventKey.new() + up.pressed = true + up.keycode = KEY_UP + InputMap.action_add_event("fsr_up", up) + + InputMap.add_action("fsr_down") + var down = InputEventKey.new() + down.pressed = true + down.keycode = KEY_DOWN + InputMap.action_add_event("fsr_down", down) + fpspp_initialized = true + + currentRID = get_tree().get_root().get_viewport_rid() + +func _process(delta): + get_tree().root.scaling_3d_mode = Viewport.SCALING_3D_MODE_FSR2 + if Input.is_action_just_pressed("fsr_up"): + fsr_scale += 0.05 + fsr_scale = clamp(fsr_scale, 0.1, 1.0) + print("FSR:", fsr_scale) + elif Input.is_action_just_pressed("fsr_down"): + fsr_scale -= 0.05 + fsr_scale = clamp(fsr_scale, 0.1, 1.0) + print("FSR:", fsr_scale) + + get_tree().root.scaling_3d_scale = fsr_scale + + if gameData.isCaching: + return + else : + if gameData.flycam: + camera.fov = 75 + Interpolate(delta) + else : + if interpolate: + Interpolate(delta) + FOV(delta) + DOF(delta) + else : + Follow() + FOV(delta) + DOF(delta) + + if camera.projection == projection: + var near_far_factor = nearFarSpeed * delta * 10 + var fov_factor = FOVSpeed * delta * 10 + var new_near = lerp(near, camera.near, near_far_factor) as float + var new_far = lerp(far, camera.far, near_far_factor) as float + var new_fov = lerp(fov, camera.fov, fov_factor) as float + set_perspective(new_fov, new_near, new_far) + +func Interpolate(delta): + var translate_factor = translateSpeed * delta * 10 + var rotate_factor = rotateSpeed * delta * 10 + var target_xform = camera.get_global_transform() + var local_transform_only_origin: = Transform3D(Basis(), get_global_transform().origin) + var local_transform_only_basis: = Transform3D(get_global_transform().basis, Vector3()) + + local_transform_only_origin = local_transform_only_origin.interpolate_with(target_xform, translate_factor) + local_transform_only_basis = local_transform_only_basis.interpolate_with(target_xform, rotate_factor) + set_global_transform(Transform3D(local_transform_only_basis.basis, local_transform_only_origin.origin)) + +func Follow(): + var local_transform_only_origin: = Transform3D(Basis(), get_global_transform().origin) + var local_transform_only_basis: = Transform3D(get_global_transform().basis, Vector3()) + var target_xform: = camera.get_global_transform() + + local_transform_only_origin = target_xform + local_transform_only_basis = target_xform + set_global_transform(Transform3D(local_transform_only_basis.basis, local_transform_only_origin.origin)) + +func FOV(delta): + + if gameData.isAiming && !gameData.isRunning && !gameData.isInspecting && !gameData.isPreparing && !gameData.isColliding && !gameData.isReloading && (gameData.weaponType == 0 || gameData.weaponType == 1): + camera.fov = lerp(camera.fov, gameData.aimFOV, delta * 50.0) + + + elif gameData.isAiming && !gameData.isRunning && !gameData.isInspecting && !gameData.isPreparing && !gameData.isColliding && (gameData.weaponType == 2 || gameData.weaponType == 3): + camera.fov = lerp(camera.fov, gameData.aimFOV, delta * 50.0) + + + else : + camera.fov = lerp(camera.fov, gameData.baseFOV, delta * 25) + +func DOF(delta): + + if (gameData.settings || gameData.interface): + UIDOF(delta) + + elif gameData.isScoped && gameData.isAiming && !gameData.isReloading && (gameData.weaponType == 0 || gameData.weaponType == 1): + ScopeDOF(delta) + + elif gameData.isScoped && gameData.isAiming && (gameData.weaponType == 2 || gameData.weaponType == 3): + ScopeDOF(delta) + + else : + ResetDOF(delta) + +func UIDOF(delta): + attribute.dof_blur_far_enabled = true + attribute.dof_blur_near_enabled = true + attribute.dof_blur_far_distance = 0.01 + attribute.dof_blur_far_transition = 5.0 + attribute.dof_blur_near_distance = 400 + attribute.dof_blur_near_transition = 1.0 + attribute.dof_blur_amount = move_toward(attribute.dof_blur_amount, 0.1, delta) + +func ScopeDOF(delta): + + attribute.dof_blur_far_enabled = true + attribute.dof_blur_near_enabled = false + attribute.dof_blur_far_distance = 0.01 + attribute.dof_blur_far_transition = 5.0 + attribute.dof_blur_amount = move_toward(attribute.dof_blur_amount, 0.1, delta) + +func ResetDOF(delta): + + attribute.dof_blur_amount = move_toward(attribute.dof_blur_amount, 0.0, delta) + + if attribute.dof_blur_amount == 0: + attribute.dof_blur_far_enabled = false + attribute.dof_blur_near_enabled = false diff --git a/mod_src/FPS++/Scripts/World.gd.remap b/mod_src/FPS++/Scripts/World.gd.remap new file mode 100644 index 0000000..f2fcd4c --- /dev/null +++ b/mod_src/FPS++/Scripts/World.gd.remap @@ -0,0 +1,2 @@ +[remap] +path="res://Scripts/World.mod.gd" diff --git a/mod_src/FPS++/Scripts/World.mod.gd b/mod_src/FPS++/Scripts/World.mod.gd new file mode 100644 index 0000000..3e05322 --- /dev/null +++ b/mod_src/FPS++/Scripts/World.mod.gd @@ -0,0 +1,855 @@ +@tool +extends Node3D + + +var audioLibrary = preload("res://Resources/AudioLibrary.tres") +var audioInstance2D = preload("res://Resources/AudioInstance2D.tscn") +var gameData = preload("res://Resources/GameData.tres") + + +const waterMaterial = preload("res://Nature/Water/Files/MT_Water.tres") +const iceMaterial = preload("res://Nature/Water/Files/MT_Ice.tres") +const debrisMaterial = preload("res://Nature/Debris/Files/MT_Debris.tres") +const debrisMaterialAA = preload("res://Nature/Debris/Files/MT_Debris_AA.tres") +const foliageMaterial = preload("res://Nature/Foliage/Files/MT_Foliage.tres") +const foliageMaterialAA = preload("res://Nature/Foliage/Files/MT_Foliage_AA.tres") +const branchesMaterial = preload("res://Nature/Trees/Files/MT_Tree_Branches.tres") +const branchesMaterialAA = preload("res://Nature/Trees/Files/MT_Tree_Branches_AA.tres") +const billboardMaterial = preload("res://Nature/Trees/Files/MT_Tree_Billboard.tres") +const billboardMaterialAA = preload("res://Nature/Trees/Files/MT_Tree_Billboard_AA.tres") + +@export_group("References") +@export var terrain: Node3D +@export var summerMaterial: Material +@export var winterMaterial: Material + +@export_group("Audio") +@export var waterAudio: Array[AudioStreamPlayer3D] + +@export_group("Skyboxes") +@export var dawnDarkSky: Material +@export var dawnNeutralSky: Material +@export var dayDarkSky: Material +@export var dayNeutralSky: Material +@export var duskDarkSky: Material +@export var duskNeutralSky: Material +@export var nightDarkSky: Material +@export var nightNeutralSky: Material + +@export_group("Seasons") +@export var summer: bool = false: + set = ExecuteSummer +@export var winter: bool = false: + set = ExecuteWinter + +@export_group("Water") +@export var showWater: bool = false: + set = ExecuteShowWater +@export var hideWater: bool = false: + set = ExecuteHideWater + +@export_group("Lighting") +@export_subgroup("Tutorial") +@export var tutorial: bool = false: + set = ExecuteTutorial +@export_subgroup("Shelter") +@export var shelter: bool = false: + set = ExecuteShelter +@export_subgroup("Neutral") +@export var dawnNeutral: bool = false: + set = ExecuteDawnNeutral +@export var dayNeutral: bool = false: + set = ExecuteDayNeutral +@export var duskNeutral: bool = false: + set = ExecuteDuskNeutral +@export var nightNeutral: bool = false: + set = ExecuteNightNeutral +@export_subgroup("Dark") +@export var dawnDark: bool = false: + set = ExecuteDawnDark +@export var dayDark: bool = false: + set = ExecuteDayDark +@export var duskDark: bool = false: + set = ExecuteDuskDark +@export var nightDark: bool = false: + set = ExecuteNightDark + +@export_group("Spawners") +@export_subgroup("Master") +@export var spawnAll: bool = false: + set = ExecuteSpawnAll +@export var clearAll: bool = false: + set = ExecuteClearAll +@export_subgroup("Rocks") +@export var spawnRocks: bool = false: + set = ExecuteSpawnRocks +@export var clearRocks: bool = false: + set = ExecuteClearRocks +@export_subgroup("Stones") +@export var spawnStones: bool = false: + set = ExecuteSpawnStones +@export var clearStones: bool = false: + set = ExecuteClearStones +@export_subgroup("Pebbles") +@export var spawnPebbles: bool = false: + set = ExecuteSpawnPebbles +@export var clearPebbles: bool = false: + set = ExecuteClearPebbles +@export_subgroup("Logs") +@export var spawnLogs: bool = false: + set = ExecuteSpawnLogs +@export var clearLogs: bool = false: + set = ExecuteClearLogs +@export_subgroup("Stumps") +@export var spawnStumps: bool = false: + set = ExecuteSpawnStumps +@export var clearStumps: bool = false: + set = ExecuteClearStumps +@export_subgroup("Debris") +@export var spawnDebris: bool = false: + set = ExecuteSpawnDebris +@export var clearDebris: bool = false: + set = ExecuteClearDebris +@export_subgroup("Foliage") +@export var spawnFoliage: bool = false: + set = ExecuteSpawnFoliage +@export var clearFoliage: bool = false: + set = ExecuteClearFoliage +@export_subgroup("Grass") +@export var spawnGrass: bool = false: + set = ExecuteSpawnGrass +@export var clearGrass: bool = false: + set = ExecuteClearGrass +@export_subgroup("Trees") +@export var spawnTrees: bool = false: + set = ExecuteSpawnTrees +@export var clearTrees: bool = false: + set = ExecuteClearTrees + +@export_group("Settings") +@export_subgroup("Rendering") +@export var RStandard: bool = false: + set = ExecuteStandardRendering +@export var RPerformance: bool = false: + set = ExecutePerformanceRendering +@export_subgroup("Lighting") +@export var LStandard: bool = false: + set = ExecuteStandardLighting +@export var LPerformance: bool = false: + set = ExecutePerformanceLighting + + +@onready var rocks = $"../Content/Spawners/Rocks" +@onready var stones = $"../Content/Spawners/Stones" +@onready var pebbles = $"../Content/Spawners/Pebbles" +@onready var logs = $"../Content/Spawners/Logs" +@onready var stumps = $"../Content/Spawners/Stumps" +@onready var debris = $"../Content/Spawners/Debris" +@onready var foliage = $"../Content/Spawners/Foliage" +@onready var grass = $"../Content/Spawners/Grass" +@onready var trees = $"../Content/Spawners/Trees" + + +@onready var environment = $Environment +@onready var sun = $Sun +@onready var water = $Water + + +@onready var ambient = $Audio / Ambient +@onready var wind = $Audio / Wind +@onready var thunder = $Audio / Thunder + + +@onready var aurora = $VFX / Aurora +@onready var rain = $VFX / Rain +@onready var storm = $VFX / Storm +@onready var snow = $VFX / Snow +@onready var blizzard = $VFX / Blizzard + +func ExecuteSummer(value: bool)->void : + RenderingServer.global_shader_parameter_set("Winter", false) + + if terrain: + terrain.get_child(0).set_surface_override_material(0, summerMaterial) + + if water.visible: + water.material = waterMaterial + + + if !Engine.is_editor_hint() && water.visible && gameData.flycam: + water.use_collision = true + else : + water.use_collision = false + + for audio in waterAudio: + audio.play() + + grass.show() + foliage.show() + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + summer = false + +func ExecuteWinter(value: bool)->void : + RenderingServer.global_shader_parameter_set("Winter", true) + + if terrain: + terrain.get_child(0).set_surface_override_material(0, winterMaterial) + + if water.visible: + water.material = iceMaterial + water.use_collision = true + + for audio in waterAudio: + audio.stop() + + grass.hide() + foliage.hide() + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + winter = false + + + +func ExecuteShowWater(value: bool)->void : + water.show() + UpdateProbes() + showWater = false + +func ExecuteHideWater(value: bool)->void : + water.hide() + UpdateProbes() + hideWater = false + + + +func ExecuteTutorial(value: bool)->void : + environment.environment.sky.sky_material = dayDarkSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 1.0 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.01 + environment.environment.volumetric_fog_albedo = Color8(0, 0, 0) + environment.environment.volumetric_fog_emission = Color8(200, 200, 200) + environment.environment.volumetric_fog_emission_energy = 0.2 + environment.environment.volumetric_fog_anisotropy = 0.0 + environment.environment.volumetric_fog_enabled = true + + sun.hide() + UpdateProbes() + tutorial = false + +func ExecuteShelter(value: bool)->void : + environment.environment.sky.sky_material = dayDarkSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.1 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_enabled = false + + sun.hide() + UpdateProbes() + shelter = false + +func ExecuteDawnDark(value: bool)->void : + environment.environment.sky.sky_material = dawnDarkSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.02 + environment.environment.volumetric_fog_albedo = Color8(0, 0, 0) + environment.environment.volumetric_fog_emission = Color8(180, 210, 240) + environment.environment.volumetric_fog_emission_energy = 0.1 + environment.environment.volumetric_fog_anisotropy = 0.9 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 0.25 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(255, 255, 255) + sun.rotation_degrees = Vector3(-25, 45, 0) + + UpdateProbes() + dawnDark = false + +func ExecuteDawnNeutral(value: bool)->void : + environment.environment.sky.sky_material = dawnNeutralSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.01 + environment.environment.volumetric_fog_albedo = Color8(120, 80, 40) + environment.environment.volumetric_fog_emission = Color8(180, 195, 220) + environment.environment.volumetric_fog_emission_energy = 0.2 + environment.environment.volumetric_fog_anisotropy = 0.9 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 1.0 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(200, 180, 180) + sun.rotation_degrees = Vector3(-25, 45, 0) + + UpdateProbes() + dawnNeutral = false + +func ExecuteDayNeutral(value: bool)->void : + environment.environment.sky.sky_material = dayNeutralSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.01 + environment.environment.volumetric_fog_albedo = Color8(150, 120, 80) + environment.environment.volumetric_fog_emission = Color8(200, 220, 240) + environment.environment.volumetric_fog_emission_energy = 0.2 + environment.environment.volumetric_fog_anisotropy = 0.9 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 1.0 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(255, 255, 255) + sun.rotation_degrees = Vector3(-45, -45, 0) + + UpdateProbes() + dayNeutral = false + +func ExecuteDayDark(value: bool)->void : + environment.environment.sky.sky_material = dayDarkSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.02 + environment.environment.volumetric_fog_albedo = Color8(0, 0, 0) + environment.environment.volumetric_fog_emission = Color8(200, 210, 220) + environment.environment.volumetric_fog_emission_energy = 0.1 + environment.environment.volumetric_fog_anisotropy = 0.9 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 0.25 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(255, 255, 255) + sun.rotation_degrees = Vector3(-45, -45, 0) + + UpdateProbes() + dayDark = false + +func ExecuteDuskNeutral(value: bool)->void : + environment.environment.sky.sky_material = duskNeutralSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.01 + environment.environment.volumetric_fog_albedo = Color8(120, 80, 40) + environment.environment.volumetric_fog_emission = Color8(200, 180, 160) + environment.environment.volumetric_fog_emission_energy = 0.2 + environment.environment.volumetric_fog_anisotropy = 0.9 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 1.0 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(220, 200, 190) + sun.rotation_degrees = Vector3(-25, -45, 0) + + UpdateProbes() + duskNeutral = false + +func ExecuteDuskDark(value: bool)->void : + environment.environment.sky.sky_material = duskDarkSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(0, 0, 0) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.02 + environment.environment.volumetric_fog_albedo = Color8(0, 0, 0) + environment.environment.volumetric_fog_emission = Color8(200, 180, 160) + environment.environment.volumetric_fog_emission_energy = 0.1 + environment.environment.volumetric_fog_anisotropy = 0.9 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 0.25 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(220, 200, 180) + sun.rotation_degrees = Vector3(-25, -45, 0) + + UpdateProbes() + duskDark = false + +func ExecuteNightNeutral(value: bool)->void : + environment.environment.sky.sky_material = nightNeutralSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(60, 70, 90) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.01 + environment.environment.volumetric_fog_albedo = Color8(0, 0, 0) + environment.environment.volumetric_fog_emission = Color8(50, 50, 50) + environment.environment.volumetric_fog_emission_energy = 0.2 + environment.environment.volumetric_fog_anisotropy = 0.0 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 1.0 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(70, 80, 90) + sun.rotation_degrees = Vector3(-45, 120, 0) + + UpdateProbes() + nightNeutral = false + +func ExecuteNightDark(value: bool)->void : + environment.environment.sky.sky_material = nightDarkSky + environment.environment.sky_rotation = Vector3(0, 0, 0) + environment.environment.ambient_light_color = Color8(60, 70, 90) + environment.environment.ambient_light_sky_contribution = 0.5 + environment.environment.ambient_light_energy = 1.0 + environment.environment.volumetric_fog_density = 0.02 + environment.environment.volumetric_fog_albedo = Color8(0, 0, 0) + environment.environment.volumetric_fog_emission = Color8(50, 50, 50) + environment.environment.volumetric_fog_emission_energy = 0.1 + environment.environment.volumetric_fog_anisotropy = 0.0 + environment.environment.volumetric_fog_enabled = true + + sun.show() + sun.light_energy = 0.5 + sun.shadow_opacity = 1.0 + sun.light_color = Color8(70, 80, 90) + sun.rotation_degrees = Vector3(-45, 120, 0) + + UpdateProbes() + nightDark = false + + + +func ExecuteSpawnAll(value: bool)->void : + clearAll = true + await get_tree().create_timer(0.1).timeout; + rocks.generate = true + await get_tree().create_timer(0.1).timeout; + stones.generate = true + await get_tree().create_timer(0.1).timeout; + pebbles.generate = true + await get_tree().create_timer(0.1).timeout; + logs.generate = true + await get_tree().create_timer(0.1).timeout; + stumps.generate = true + await get_tree().create_timer(0.1).timeout; + debris.generate = true + await get_tree().create_timer(0.1).timeout; + foliage.generate = true + await get_tree().create_timer(0.1).timeout; + grass.generate = true + await get_tree().create_timer(0.1).timeout; + trees.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnAll = false + +func ExecuteClearAll(value: bool)->void : + rocks.clear = true + stones.clear = true + pebbles.clear = true + logs.clear = true + stumps.clear = true + debris.clear = true + foliage.clear = true + grass.clear = true + trees.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearAll = false + +func ExecuteSpawnRocks(value: bool)->void : + rocks.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnRocks = false + +func ExecuteClearRocks(value: bool)->void : + rocks.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearRocks = false + +func ExecuteSpawnStones(value: bool)->void : + stones.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnStones = false + +func ExecuteClearStones(value: bool)->void : + stones.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearStones = false + +func ExecuteSpawnPebbles(value: bool)->void : + pebbles.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnPebbles = false + +func ExecuteClearPebbles(value: bool)->void : + pebbles.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearPebbles = false + +func ExecuteSpawnLogs(value: bool)->void : + logs.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnLogs = false + +func ExecuteClearLogs(value: bool)->void : + logs.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearLogs = false + +func ExecuteSpawnStumps(value: bool)->void : + stumps.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnStumps = false + +func ExecuteClearStumps(value: bool)->void : + stumps.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearStumps = false + +func ExecuteSpawnDebris(value: bool)->void : + debris.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnDebris = false + +func ExecuteClearDebris(value: bool)->void : + debris.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearDebris = false + +func ExecuteSpawnFoliage(value: bool)->void : + foliage.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnFoliage = false + +func ExecuteClearFoliage(value: bool)->void : + foliage.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearFoliage = false + +func ExecuteSpawnGrass(value: bool)->void : + grass.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnGrass = false + +func ExecuteClearGrass(value: bool)->void : + grass.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearGrass = false + +func ExecuteSpawnTrees(value: bool)->void : + trees.generate = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + spawnTrees = false + +func ExecuteClearTrees(value: bool)->void : + trees.clear = true + await get_tree().create_timer(0.1).timeout; + UpdateProbes() + clearTrees = false + +func UpdateProbes()->void : + var probes = get_tree().get_nodes_in_group("Probe") + + for probe in probes: + probe.interior = true + probe.interior = false + + + +func ExecuteStandardRendering(value: bool)->void : + get_tree().get_root().msaa_3d = Viewport.MSAA_4X + + RenderingServer.directional_shadow_atlas_set_size(4096, true) + sun.directional_shadow_max_distance = 200 + + for element in debris.get_children(): + element.set_surface_override_material(0, debrisMaterialAA) + + for element in grass.get_children(): + element.set_surface_override_material(0, foliageMaterialAA) + + for element in trees.get_children(): + element.get_child(0).set_surface_override_material(0, branchesMaterialAA) + element.get_child(1).set_surface_override_material(0, billboardMaterialAA) + + + + RStandard = false + +func ExecutePerformanceRendering(value: bool)->void : + get_tree().get_root().msaa_3d = Viewport.MSAA_DISABLED + + RenderingServer.directional_shadow_atlas_set_size(2048, true) + sun.directional_shadow_max_distance = 100 + + for element in debris.get_children(): + element.set_surface_override_material(0, debrisMaterial) + + for element in grass.get_children(): + element.set_surface_override_material(0, foliageMaterial) + + for element in trees.get_children(): + element.get_child(0).set_surface_override_material(0, branchesMaterial) + element.get_child(1).set_surface_override_material(0, billboardMaterial) + + RPerformance = false + +func ExecuteStandardLighting(value: bool)->void : + environment.environment.ssao_enabled = true + environment.environment.ssil_enabled = true + + LStandard = false + +func ExecutePerformanceLighting(value: bool)->void : + environment.environment.ssao_enabled = false + environment.environment.ssil_enabled = false + + LPerformance = false + + + +func UpdateAmbientSFX(): + ambient.stop() + wind.stop() + thunder.stop() + + if gameData.season == 1: + + + if gameData.weather == 1: + if gameData.TOD == 1: + PlayAmbientDawn() + elif gameData.TOD == 2: + PlayAmbientDay() + elif gameData.TOD == 3: + PlayAmbientDusk() + elif gameData.TOD == 4: + PlayAmbientNight() + + + elif gameData.weather == 2: + if gameData.TOD == 1: + PlayWindMedium() + elif gameData.TOD == 2: + PlayWindMedium() + elif gameData.TOD == 3: + PlayWindMedium() + elif gameData.TOD == 4: + PlayWindMedium() + + + elif gameData.weather == 3: + if gameData.TOD == 1: + PlayRainLight() + PlayWindMedium() + elif gameData.TOD == 2: + PlayRainLight() + PlayWindMedium() + elif gameData.TOD == 3: + PlayRainLight() + PlayWindMedium() + elif gameData.TOD == 4: + PlayRainLight() + PlayWindMedium() + + + elif gameData.weather == 4: + if gameData.TOD == 1: + PlayRainLight() + PlayWindHeavy() + PlayThunderHum() + elif gameData.TOD == 2: + PlayRainLight() + PlayWindHeavy() + PlayThunderHum() + elif gameData.TOD == 3: + PlayRainLight() + PlayWindHeavy() + PlayThunderHum() + elif gameData.TOD == 4: + PlayRainLight() + PlayWindHeavy() + PlayThunderHum() + + elif gameData.season == 2: + + + if gameData.weather == 1: + if gameData.TOD == 1: + PlayWindMedium() + elif gameData.TOD == 2: + PlayWindMedium() + elif gameData.TOD == 3: + PlayWindMedium() + elif gameData.TOD == 4: + PlayWindMedium() + + + elif gameData.weather == 2: + if gameData.TOD == 1: + PlayWindMedium() + elif gameData.TOD == 2: + PlayWindMedium() + elif gameData.TOD == 3: + PlayWindMedium() + elif gameData.TOD == 4: + PlayWindMedium() + + + elif gameData.weather == 3: + if gameData.TOD == 1: + PlayWindMedium() + elif gameData.TOD == 2: + PlayWindMedium() + elif gameData.TOD == 3: + PlayWindMedium() + elif gameData.TOD == 4: + PlayWindMedium() + + + elif gameData.weather == 4: + if gameData.TOD == 1: + PlayWindHowl() + elif gameData.TOD == 2: + PlayWindHowl() + elif gameData.TOD == 3: + PlayWindHowl() + elif gameData.TOD == 4: + PlayWindHowl() + +func PlayAmbientDawn(): + var randomIndex: int = randi_range(0, audioLibrary.ambientDawn.audioClips.size() - 1) + ambient.stream = audioLibrary.ambientDawn.audioClips[randomIndex] + ambient.volume_db = randf_range(audioLibrary.ambientDawn.minVolume, audioLibrary.ambientDawn.maxVolume) + ambient.pitch_scale = randf_range(audioLibrary.ambientDawn.minPitch, audioLibrary.ambientDawn.maxPitch) + ambient.play() + +func PlayAmbientDay(): + var randomIndex: int = randi_range(0, audioLibrary.ambientDay.audioClips.size() - 1) + ambient.stream = audioLibrary.ambientDay.audioClips[randomIndex] + ambient.volume_db = randf_range(audioLibrary.ambientDay.minVolume, audioLibrary.ambientDay.maxVolume) + ambient.pitch_scale = randf_range(audioLibrary.ambientDay.minPitch, audioLibrary.ambientDay.maxPitch) + ambient.play() + +func PlayAmbientDusk(): + var randomIndex: int = randi_range(0, audioLibrary.ambientDusk.audioClips.size() - 1) + ambient.stream = audioLibrary.ambientDusk.audioClips[randomIndex] + ambient.volume_db = randf_range(audioLibrary.ambientDusk.minVolume, audioLibrary.ambientDusk.maxVolume) + ambient.pitch_scale = randf_range(audioLibrary.ambientDusk.minPitch, audioLibrary.ambientDusk.maxPitch) + ambient.play() + +func PlayAmbientNight(): + var randomIndex: int = randi_range(0, audioLibrary.ambientNight.audioClips.size() - 1) + ambient.stream = audioLibrary.ambientNight.audioClips[randomIndex] + ambient.volume_db = randf_range(audioLibrary.ambientNight.minVolume, audioLibrary.ambientNight.maxVolume) + ambient.pitch_scale = randf_range(audioLibrary.ambientNight.minPitch, audioLibrary.ambientNight.maxPitch) + ambient.play() + +func PlayWindLight(): + var randomIndex: int = randi_range(0, audioLibrary.windLight.audioClips.size() - 1) + wind.stream = audioLibrary.windLight.audioClips[randomIndex] + wind.volume_db = randf_range(audioLibrary.windLight.minVolume, audioLibrary.windLight.maxVolume) + wind.pitch_scale = randf_range(audioLibrary.windLight.minPitch, audioLibrary.windLight.maxPitch) + wind.play() + +func PlayWindMedium(): + var randomIndex: int = randi_range(0, audioLibrary.windMedium.audioClips.size() - 1) + wind.stream = audioLibrary.windMedium.audioClips[randomIndex] + wind.volume_db = randf_range(audioLibrary.windMedium.minVolume, audioLibrary.windMedium.maxVolume) + wind.pitch_scale = randf_range(audioLibrary.windMedium.minPitch, audioLibrary.windMedium.maxPitch) + wind.play() + +func PlayWindHeavy(): + var randomIndex: int = randi_range(0, audioLibrary.windHeavy.audioClips.size() - 1) + wind.stream = audioLibrary.windHeavy.audioClips[randomIndex] + wind.volume_db = randf_range(audioLibrary.windHeavy.minVolume, audioLibrary.windHeavy.maxVolume) + wind.pitch_scale = randf_range(audioLibrary.windHeavy.minPitch, audioLibrary.windHeavy.maxPitch) + wind.play() + +func PlayWindHowl(): + var randomIndex: int = randi_range(0, audioLibrary.windHowl.audioClips.size() - 1) + wind.stream = audioLibrary.windHowl.audioClips[randomIndex] + wind.volume_db = randf_range(audioLibrary.windHowl.minVolume, audioLibrary.windHowl.maxVolume) + wind.pitch_scale = randf_range(audioLibrary.windHowl.minPitch, audioLibrary.windHowl.maxPitch) + wind.play() + +func PlayRainLight(): + var randomIndex: int = randi_range(0, audioLibrary.rainLight.audioClips.size() - 1) + ambient.stream = audioLibrary.rainLight.audioClips[randomIndex] + ambient.volume_db = randf_range(audioLibrary.rainLight.minVolume, audioLibrary.rainLight.maxVolume) + ambient.pitch_scale = randf_range(audioLibrary.rainLight.minPitch, audioLibrary.rainLight.maxPitch) + ambient.play() + +func PlayRainHeavy(): + var randomIndex: int = randi_range(0, audioLibrary.rainHeavy.audioClips.size() - 1) + ambient.stream = audioLibrary.rainHeavy.audioClips[randomIndex] + ambient.volume_db = randf_range(audioLibrary.rainHeavy.minVolume, audioLibrary.rainHeavy.maxVolume) + ambient.pitch_scale = randf_range(audioLibrary.rainHeavy.minPitch, audioLibrary.rainHeavy.maxPitch) + ambient.play() + +func PlayThunderHum(): + var randomIndex: int = randi_range(0, audioLibrary.thunderHum.audioClips.size() - 1) + thunder.stream = audioLibrary.thunderHum.audioClips[randomIndex] + thunder.volume_db = randf_range(audioLibrary.thunderHum.minVolume, audioLibrary.thunderHum.maxVolume) + thunder.pitch_scale = randf_range(audioLibrary.thunderHum.minPitch, audioLibrary.thunderHum.maxPitch) + thunder.play() + +func PlayThunderStrike(): + var thunderStrike = audioInstance2D.instantiate() + add_child(thunderStrike) + thunderStrike.PlayInstance(audioLibrary.thunderStrike) + + + +func ResetVFX(): + rain.hide() + storm.hide() + snow.hide() + blizzard.hide() + aurora.hide() + RenderingServer.global_shader_parameter_set("Storm", false) + +func Rain(): + rain.show() + +func Storm(): + storm.show() + RenderingServer.global_shader_parameter_set("Storm", true) + +func Snow(): + snow.show() + +func Blizzard(): + blizzard.show() + +func Aurora(): + aurora.show() diff --git a/mod_src/SimpleControls/Scripts/Character.gd.remap b/mod_src/SimpleControls/Scripts/Character.gd.remap new file mode 100644 index 0000000..406d2f5 --- /dev/null +++ b/mod_src/SimpleControls/Scripts/Character.gd.remap @@ -0,0 +1,2 @@ +[remap] +path="res://Scripts/Character.mod.gd" diff --git a/mod_src/SimpleControls/Scripts/Character.mod.gd b/mod_src/SimpleControls/Scripts/Character.mod.gd new file mode 100644 index 0000000..3888a0d --- /dev/null +++ b/mod_src/SimpleControls/Scripts/Character.mod.gd @@ -0,0 +1,390 @@ +extends Node3D + + +var gameData = preload("res://Resources/GameData.tres") +var audioLibrary = preload("res://Resources/AudioLibrary.tres") +var audioInstance2D = preload("res://Resources/AudioInstance2D.tscn") + + +var heavyGear = false + + +@onready var interface = $"../../UI/UI_Interface" +@onready var weapons = $"../../Camera/Weapons" +@onready var audio = $"../../Audio" + +func _physics_process(delta): + if !gameData.isCaching && !gameData.flycam: + Health(delta) + Stamina(delta) + Energy(delta) + Hydration(delta) + Oxygen(delta) + BurnDamage(delta) + + +func Health(delta): + if gameData.starvation && !gameData.isDead: + gameData.health -= delta / 10 + + if gameData.dehydration && !gameData.isDead: + gameData.health -= delta / 10 + + if gameData.bleeding && !gameData.isDead: + gameData.health -= delta / 5 + + if gameData.fracture && !gameData.isDead: + gameData.health -= delta / 5 + + if gameData.burn && !gameData.isDead: + gameData.health -= delta / 5 + + if gameData.rupture && !gameData.isDead: + gameData.health -= delta + + if gameData.headshot && !gameData.isDead: + gameData.health -= delta + + if gameData.health <= 0 && !gameData.isDead: + Death() + +func Energy(delta): + if !gameData.starvation: + gameData.energy -= delta / 30.0 + + if gameData.energy <= 0 && !gameData.starvation: + Starvation(true) + elif gameData.energy > 0 && gameData.starvation: + Starvation(false) + +func Hydration(delta): + if !gameData.dehydration: + gameData.hydration -= delta / 20.0 + + if gameData.hydration <= 0 && !gameData.dehydration: + Dehydration(true) + elif gameData.hydration > 0 && gameData.dehydration: + Dehydration(false) + +func Stamina(delta): + gameData.weaponPosition = 1 if gameData.isRunning else 2 + if (gameData.isRunning || gameData.overweight || (gameData.isSwimming && gameData.isMoving)) && gameData.bodyStamina > 0: + + if gameData.overweight || gameData.starvation || gameData.dehydration: + gameData.bodyStamina -= delta * 4.0 + else : + gameData.bodyStamina -= delta * 2.0 + + + elif gameData.bodyStamina < 100: + if gameData.starvation || gameData.dehydration: + gameData.bodyStamina += delta * 5.0 + else : + gameData.bodyStamina += delta * 10.0 + + + if ((gameData.primary || gameData.secondary) && (gameData.isAiming || gameData.isCanted || gameData.isInspecting || gameData.overweight) || (gameData.isSwimming && gameData.isMoving)) && gameData.armStamina > 0: + + if gameData.overweight || gameData.starvation || gameData.dehydration: + gameData.armStamina -= delta * 4.0 + else : + gameData.armStamina -= delta * 2.0 + + + elif gameData.armStamina < 100: + + if gameData.starvation || gameData.dehydration: + gameData.armStamina += delta * 10.0 + else : + gameData.armStamina += delta * 20.0 + + gameData.bodyStamina = clampf(gameData.bodyStamina, 0, 100) + gameData.armStamina = clampf(gameData.armStamina, 0, 100) + +func Oxygen(delta): + + if gameData.isSubmerged: + if gameData.isSwimming: + gameData.oxygen -= delta * 4.0 + else : + gameData.oxygen -= delta * 2.0 + + + elif gameData.oxygen < 100: + gameData.oxygen += delta * 50.0 + + + if gameData.oxygen <= 0 && !gameData.isDead: + Death() + + + + +func Death(): + + if gameData.flycam: + return + + PlayDeathAudio() + audio.breathing.stop() + audio.heartbeat.stop() + gameData.health = 0 + gameData.isDead = true + gameData.freeze = true + weapons.ClearWeapons() + + + if !gameData.permadeath && !gameData.shelter && !gameData.tutorial: + Loader.ResetGear() + print("DEATH: Standard") + + + if gameData.shelter: + Loader.ResetGear() + Loader.SaveShelter() + print("DEATH: Shelter") + + + if gameData.permadeath: + Loader.ResetSave() + print("DEATH: Vostok") + + Loader.LoadScene("Death") + + + + +func Consume(item: ItemData): + gameData.health += item.health + gameData.energy += item.energy + gameData.hydration += item.hydration + + gameData.health = clampf(gameData.health, 0, 100) + gameData.energy = clampf(gameData.energy, 0, 100) + gameData.hydration = clampf(gameData.hydration, 0, 100) + + if item.bleeding: + Bleeding(false) + if item.burn: + Burn(false) + if item.fracture: + Fracture(false) + if item.rupture: + Rupture(false) + if item.headshot: + Headshot(false) + + + +func ExplosionDamage(): + + if !gameData.bleeding: + Bleeding(true) + + gameData.damage = true + gameData.health -= 40.0 + +func BurnDamage(delta): + if gameData.isBurning: + gameData.damage = true + gameData.health -= delta * 10 + +func FallDamage(distance: float): + if distance > 10: + gameData.health = 0.0 + gameData.damage = true + Fracture(true) + elif distance > 5: + gameData.health -= randi_range(5, 20) + gameData.damage = true + Fracture(true) + +func WeaponDamage(damage: int, penetration: int): + + var hitbox = randi_range(1, 3) + + + + if hitbox == 1: + print("Hit: HEAD") + + + if interface.HelmetCheck(penetration): + gameData.impact = true + PlayArmorAudio() + return + + + + elif hitbox == 2: + print("Hit: TORSO") + + + if interface.PlateCheck(penetration): + gameData.impact = true + PlayArmorAudio() + return + + elif hitbox == 3: + print("Hit: LIMBS") + + + + var medicalRoll = randi_range(0, 100) + + + if hitbox == 1 && medicalRoll < 5 && !gameData.headshot: + Headshot(true) + + elif hitbox == 2 && medicalRoll < 5 && !gameData.rupture: + Rupture(true) + + else : + + if medicalRoll > 0 && medicalRoll <= 5 && !gameData.bleeding: + Bleeding(true) + + elif medicalRoll > 5 && medicalRoll <= 10 && !gameData.fracture: + Fracture(true) + else : + PlayImpactAudio() + + + + if !gameData.isDead: + gameData.damage = true + gameData.health -= randf_range(damage / 4.0, damage / 2.0) + gameData.health = clampf(gameData.health, 0, 100) + + + + +func Overweight(active: bool): + if active: + gameData.overweight = true + PlayIndicator() + PlayOverweight() + else : + gameData.overweight = false + +func Starvation(active: bool): + if active: + gameData.starvation = true + PlayIndicator() + PlayStarvation() + else : + gameData.starvation = false + +func Dehydration(active: bool): + if active: + gameData.dehydration = true + PlayIndicator() + PlayDehydration() + else : + gameData.dehydration = false + +func Bleeding(active: bool): + if active: + gameData.bleeding = true + PlayIndicator() + PlayBleeding() + else : + gameData.bleeding = false + +func Fracture(active: bool): + if active: + gameData.fracture = true + PlayIndicator() + PlayFracture() + else : + gameData.fracture = false + +func Burn(active: bool): + if active: + gameData.burn = true + PlayIndicator() + PlayBurn() + else : + gameData.burn = false + +func Rupture(active: bool): + if active: + gameData.rupture = true + PlayIndicator() + PlayRupture() + else : + gameData.rupture = false + +func Headshot(active: bool): + if active: + gameData.headshot = true + PlayIndicator() + PlayRupture() + else : + gameData.headshot = false + + + + + + + +func PlayDamageAudio(): + var damage = audioInstance2D.instantiate() + add_child(damage) + damage.PlayInstance(audioLibrary.damage) + +func PlayImpactAudio(): + var impact = audioInstance2D.instantiate() + add_child(impact) + impact.PlayInstance(audioLibrary.impact) + +func PlayArmorAudio(): + var armor = audioInstance2D.instantiate() + add_child(armor) + armor.PlayInstance(audioLibrary.armor) + +func PlayDeathAudio(): + var death = audioInstance2D.instantiate() + add_child(death) + death.PlayInstance(audioLibrary.death) + +func PlayIndicator(): + var indicator = audioInstance2D.instantiate() + add_child(indicator) + indicator.PlayInstance(audioLibrary.indicator) + +func PlayOverweight(): + var overweight = audioInstance2D.instantiate() + add_child(overweight) + overweight.PlayInstance(audioLibrary.overweight) + +func PlayStarvation(): + var starvation = audioInstance2D.instantiate() + add_child(starvation) + starvation.PlayInstance(audioLibrary.starvation) + +func PlayDehydration(): + var dehydration = audioInstance2D.instantiate() + add_child(dehydration) + dehydration.PlayInstance(audioLibrary.dehydration) + +func PlayBleeding(): + var bleeding = audioInstance2D.instantiate() + add_child(bleeding) + bleeding.PlayInstance(audioLibrary.bleeding) + +func PlayFracture(): + var fracture = audioInstance2D.instantiate() + add_child(fracture) + fracture.PlayInstance(audioLibrary.fracture) + +func PlayBurn(): + var burn = audioInstance2D.instantiate() + add_child(burn) + burn.PlayInstance(audioLibrary.burn) + +func PlayRupture(): + var rupture = audioInstance2D.instantiate() + add_child(rupture) + rupture.PlayInstance(audioLibrary.rupture) diff --git a/mod_src/SimpleControls/Scripts/Weapon.gd.remap b/mod_src/SimpleControls/Scripts/Weapon.gd.remap new file mode 100644 index 0000000..79ccf76 --- /dev/null +++ b/mod_src/SimpleControls/Scripts/Weapon.gd.remap @@ -0,0 +1,2 @@ +[remap] +path="res://Scripts/Weapon.mod.gd" diff --git a/mod_src/SimpleControls/Scripts/Weapon.mod.gd b/mod_src/SimpleControls/Scripts/Weapon.mod.gd new file mode 100644 index 0000000..9487656 --- /dev/null +++ b/mod_src/SimpleControls/Scripts/Weapon.mod.gd @@ -0,0 +1,594 @@ +extends Node3D + +var gameData = preload("res://Resources/GameData.tres") +var audioLibrary = preload("res://Resources/AudioLibrary.tres") +var audioInstance2D = preload("res://Resources/AudioInstance2D.tscn") + +@export_group("Data") +@export var weaponData: Resource + +@export_group("References") +@export var animator: AnimationTree +@export var skeleton: Skeleton3D +@export var recoil: Node3D +@export var ejector: Node3D +@export var muzzle: Node3D +@export var raycast: RayCast3D +@export var collision: RayCast3D +@export var arms: MeshInstance3D +@export var attachments: Node3D + +@export_group("Dynamic Rig") +@export var dynamicSlide: bool +@export var dynamicSelector: bool +@export var slideIndex = 0 +@export var selectorIndex = 0 +@export var backSightIndex = 0 +@export var frontSightIndex = 0 + +@export_group("Upgrade") +@export var defaultParts: Array[MeshInstance3D] +@export var upgradeParts: Array[MeshInstance3D] + +var fireRate = 0.0 +var fireTimer = 0.0 +var fireImpulse = 0.0 +var fireImpulseTimer = 0.0 +var slideValue = 0.0 +var slideWeight = 0.0 +var selectorValue = 0.0 +var initialSelectorRotation = Vector3.ZERO +var targetSelectorRotation = Vector3.ZERO +var casingDelay = 0.5 +var slideLocked = false +var UIManager +var interface +var weaponManager +var weaponSlot + +var isUpgraded = false +var activeOptic +var activeMuzzle +var activeBarrel +var aimOffset = 0.0 +var aimPosition: Vector3 +var muzzlePosition: Vector3 + +func _ready(): + weaponManager = get_parent() + + if gameData.primary: + weaponSlot = weaponManager.primarySlot + elif gameData.secondary: + weaponSlot = weaponManager.secondarySlot + + animator.active = false + gameData.weaponPosition = 1 + gameData.inspectPosition = 1 + gameData.isPreparing = false + gameData.isReloading = false + gameData.weaponType = weaponData.action + muzzlePosition = muzzle.position + + interface = get_tree().current_scene.get_node("/root/Map/Core/UI/UI_Interface") + UIManager = get_tree().current_scene.get_node("/root/Map/Core/UI") + + initialSelectorRotation = skeleton.get_bone_pose_rotation(selectorIndex).get_euler() + +func _input(event): + if gameData.isInspecting: + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_WHEEL_UP: + if gameData.inspectPosition == 1: + animator["parameters/conditions/Inspect_Front"] = false + animator["parameters/conditions/Inspect_Back"] = true + gameData.inspectPosition = 2 + + if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: + if gameData.inspectPosition == 2: + animator["parameters/conditions/Inspect_Front"] = true + animator["parameters/conditions/Inspect_Back"] = false + gameData.inspectPosition = 1 + +func _physics_process(delta): + if gameData.freeze || gameData.isCaching || gameData.isPlacing: + return + + if weaponSlot: + gameData.firemode = weaponSlot.slotData.firemode + + FireInput() + ReloadInput() + FireTimer(delta) + FireImpulse(delta) + + if dynamicSlide && !gameData.isCaching: + Slide(delta) + + if dynamicSelector && !gameData.isCaching: + Selector(delta) + +func FireInput(): + + if Input.is_action_just_pressed(("firemode")) && weaponData.action == 0 && !gameData.isReloading: + ChangeFiremode() + + if gameData.isInspecting || gameData.isReloading || gameData.isPreparing || gameData.isInserting: + return + + if !gameData.weaponPosition == 2 && !gameData.isAiming && !gameData.isCanted: + return + + if weaponSlot.slotData.ammo == 0: + return + + if weaponData.action == 0: + + if weaponSlot.slotData.firemode == 1: + if Input.is_action_just_pressed(("fire")): + FireEvent() + fireImpulse = 0.1 + fireRate = 0.1 + + elif weaponSlot.slotData.firemode == 2: + if Input.is_action_pressed(("fire")): + FireEvent() + fireImpulse = weaponData.fireRate + fireRate = weaponData.fireRate + + elif weaponData.action == 1: + + if Input.is_action_just_pressed(("fire")): + FireEvent() + fireImpulse = 0.1 + fireRate = 0.1 + + elif (weaponData.action == 2 || weaponData.action == 3) && weaponSlot.slotData.chamber == 1: + if Input.is_action_just_pressed(("fire")): + FireEvent() + fireImpulse = 0.1 + fireRate = 0.1 + +func InspectWeapon(): + animator.active = true + weaponManager.flashTimer = 0.0 + weaponManager.muzzleFlash = false + + animator["parameters/conditions/Inspect_Front"] = true + animator["parameters/conditions/Inspect_Idle"] = false + +func ResetInspect(): + if gameData.inspectPosition == 1: + animator["parameters/conditions/Inspect_Front"] = false + animator["parameters/conditions/Inspect_Idle"] = true + elif gameData.inspectPosition == 2: + animator["parameters/conditions/Inspect_Back"] = false + animator["parameters/conditions/Inspect_Idle"] = true + gameData.inspectPosition = 1 + +func ReloadInput(): + if gameData.isInspecting || gameData.isReloading || gameData.isInserting: + return + + # Pistol/Rifle reload + if Input.is_action_just_pressed(("reload")) && (weaponData.action == 0 || weaponData.action == 1): + + if weaponSlot.slotData.ammo == weaponData.magazineSize: + return + + if !interface.AmmoCheck(weaponData): + return + + animator.active = true + gameData.isReloading = true + gameData.isAiming = false + gameData.isFiring = false + + if weaponSlot.slotData.ammo != 0: + animator["parameters/conditions/Reload_Tactical"] = true + else : + animator["parameters/conditions/Reload_Empty"] = true + + var ammoNeeded = weaponData.magazineSize - weaponSlot.slotData.ammo + var ammoProvided = interface.Reload(ammoNeeded, weaponData) + weaponSlot.slotData.ammo = weaponSlot.slotData.ammo + ammoProvided + + # Chamber next round + if (weaponData.action == 2 || weaponData.action == 3) && ( !gameData.isPreparing && !gameData.isInserting): + if !(weaponSlot.slotData.ammo == 0 || weaponSlot.slotData.ammo == weaponData.magazineSize || weaponSlot.slotData.chamber == 1): + animator.active = true + gameData.isReloading = true + gameData.isFiring = false + animator["parameters/conditions/Reload"] = true + animator["parameters/conditions/Insert_Start"] = false + animator["parameters/conditions/Insert"] = false + animator["parameters/conditions/Insert_End"] = false + weaponSlot.slotData.chamber = 1 + + # Start shotgun/sniper reload + if Input.is_action_just_pressed(("reload")) && (weaponData.action == 2 || weaponData.action == 3): + + if gameData.isInserting: + return + + animator.active = true + + if !gameData.isPreparing: + animator["parameters/conditions/Insert_Start"] = true + animator["parameters/conditions/Insert_End"] = false + gameData.isFiring = false + gameData.isPreparing = true + + # Insert round + if (weaponData.action == 2 || weaponData.action == 3): + + if !gameData.isPreparing: + return + + # Full or no more ammo, exit out of reload + if weaponSlot.slotData.ammo == weaponData.magazineSize || !interface.AmmoCheck(weaponData): + animator["parameters/conditions/Insert_Start"] = false + animator["parameters/conditions/Insert_End"] = true + gameData.isFiring = false + gameData.isPreparing = false + return + + animator.active = true + animator["parameters/conditions/Insert"] = true + gameData.isInserting = true + gameData.isFiring = false + weaponSlot.slotData.chamber = 1 + + var ammoProvided = interface.Reload(1, weaponData) + weaponSlot.slotData.ammo += ammoProvided + +func FireTimer(delta): + if (fireTimer < fireRate): + fireTimer += delta + +func FireEvent(): + if fireTimer < fireRate || weaponSlot.slotData.ammo == 0: + return + + if weaponData.action == 3: + for ray in 6: + Raycast(0.5) + raycast.force_raycast_update() + else : + Raycast(0.0) + + MuzzleEffect() + PlayFireAudio() + PlayTailAudio() + recoil.ApplyRecoil() + weaponSlot.slotData.ammo -= 1 + fireTimer = 0.0 + + if weaponData.action == 0 || weaponData.action == 1: + CartridgeEject() + PlayCasingDrop() + + if weaponData.action == 2 || weaponData.action == 3: + weaponSlot.slotData.chamber = 0 + + if weaponSlot.slotData.ammo == 0 && weaponData.action == 1: + PlaySlideLockedAudio() + + if activeMuzzle == null && !weaponData.nativeSuppressor: + weaponManager.muzzleFlash = true + +func Raycast(spread: float): + raycast.rotation_degrees.x = randf_range( - spread, spread) + raycast.rotation_degrees.y = randf_range( - spread, spread) + + if raycast.is_colliding(): + var hitCollider = raycast.get_collider() + var hitPoint = raycast.get_collision_point() + var hitNormal = raycast.get_collision_normal() + var hitSurface = raycast.get_collider().get("surfaceType") + BulletDecal(hitCollider, hitPoint, hitNormal, hitSurface) + + if hitCollider is Hitbox: + if activeBarrel != null: + hitCollider.ApplyDamage(weaponData.upgrade.damage) + else : + hitCollider.ApplyDamage(weaponData.damage) + + if hitCollider.owner is Mine: + hitCollider.owner.InstantDetonate() + +func BulletDecal(hitCollider, hitPoint, hitNormal, hitSurface): + var bulletDecal + + if hitCollider is Hitbox: + bulletDecal = weaponManager.hitBlood.instantiate() + else : + bulletDecal = weaponManager.hit.instantiate() + + hitCollider.add_child(bulletDecal) + bulletDecal.global_transform.origin = hitPoint + + if hitNormal == Vector3(0, 1, 0): + bulletDecal.look_at(hitPoint + hitNormal, Vector3.RIGHT) + elif hitNormal == Vector3(0, -1, 0): + bulletDecal.look_at(hitPoint + hitNormal, Vector3.RIGHT) + else : + bulletDecal.look_at(hitPoint + hitNormal, Vector3.DOWN) + + bulletDecal.global_rotation.z = randf_range(-360, 360) + + if hitCollider is Hitbox: + bulletDecal.Emit() + else : + bulletDecal.PlayHit(hitSurface) + +func CartridgeEject(): + + if weaponData.action == 0 || weaponData.action == 1: + if weaponData.cartridge == 0: + var cartridge = weaponManager.cartridgePistol.instantiate() + ejector.add_child(cartridge) + cartridge.Emit() + elif weaponData.cartridge == 1: + var cartridge = weaponManager.cartridgeRifle.instantiate() + ejector.add_child(cartridge) + cartridge.Emit() + + if weaponData.action == 2: + var cartridge = weaponManager.cartridgeRifle.instantiate() + ejector.add_child(cartridge) + cartridge.Emit() + + if weaponData.action == 3: + var cartridge = weaponManager.cartridgeShell.instantiate() + ejector.add_child(cartridge) + cartridge.Emit() + +func MuzzleEffect(): + var newSmoke = weaponManager.smoke.instantiate() + muzzle.add_child(newSmoke) + newSmoke.Emit() + + if activeMuzzle == null && !weaponData.nativeSuppressor: + if weaponData.action == 1: + var newFlashSmall = weaponManager.flashSmall.instantiate() + muzzle.add_child(newFlashSmall) + newFlashSmall.Emit() + else : + var newFlashMedium = weaponManager.flashMedium.instantiate() + muzzle.add_child(newFlashMedium) + newFlashMedium.Emit() + +func ChangeFiremode(): + if weaponSlot.slotData.firemode == 1: + weaponSlot.slotData.firemode = 2 + elif weaponSlot.slotData.firemode == 2: + weaponSlot.slotData.firemode = 1 + + PlayFiremode() + +func PlayFireAudio(): + var fire = audioInstance2D.instantiate() + get_tree().get_root().add_child(fire) + + if isUpgraded: + if activeMuzzle != null: + fire.PlayInstance(weaponData.upgrade.fireSuppressed) + else : + if weaponSlot.slotData.firemode == 2 && weaponData.action == 0: + fire.PlayInstance(weaponData.upgrade.fireAuto) + else : + fire.PlayInstance(weaponData.upgrade.fireSemi) + + else : + if activeMuzzle != null: + fire.PlayInstance(weaponData.fireSuppressed) + else : + if weaponSlot.slotData.firemode == 2 && weaponData.action == 0: + fire.PlayInstance(weaponData.fireAuto) + else : + fire.PlayInstance(weaponData.fireSemi) + +func PlayTailAudio(): + var tail = audioInstance2D.instantiate() + get_tree().get_root().add_child(tail) + + if isUpgraded: + if activeMuzzle != null: + if gameData.indoor: + tail.PlayInstance(weaponData.upgrade.tailIndoorSuppressed) + else : + tail.PlayInstance(weaponData.upgrade.tailOutdoorSuppressed) + else : + if gameData.indoor: + tail.PlayInstance(weaponData.upgrade.tailIndoor) + else : + tail.PlayInstance(weaponData.upgrade.tailOutdoor) + + else : + if activeMuzzle != null: + if gameData.indoor: + tail.PlayInstance(weaponData.tailIndoorSuppressed) + else : + tail.PlayInstance(weaponData.tailOutdoorSuppressed) + else : + if gameData.indoor: + tail.PlayInstance(weaponData.tailIndoor) + else : + tail.PlayInstance(weaponData.tailOutdoor) + +func PlayFiremode(): + var firemode = audioInstance2D.instantiate() + add_child(firemode) + firemode.PlayInstance(audioLibrary.firemode) + +func PlayMagazineOutAudio(): + var magazineOut = audioInstance2D.instantiate() + add_child(magazineOut) + magazineOut.PlayInstance(weaponData.magazineOut) + +func PlayMagazineInAudio(): + var magazineIn = audioInstance2D.instantiate() + add_child(magazineIn) + magazineIn.PlayInstance(weaponData.magazineIn) + +func PlayAdditionalAudio(): + var additional = audioInstance2D.instantiate() + add_child(additional) + additional.PlayInstance(weaponData.additional) + +func PlaySlideReleaseAudio(): + var slideRelease = audioInstance2D.instantiate() + add_child(slideRelease) + slideRelease.PlayInstance(weaponData.slideRelease) + +func PlaySlideLockedAudio(): + var slideLockedAudio = audioInstance2D.instantiate() + add_child(slideLockedAudio) + slideLockedAudio.PlayInstance(weaponData.slideLocked) + +func PlayBoltOpenAudio(): + var boltOpen = audioInstance2D.instantiate() + add_child(boltOpen) + boltOpen.PlayInstance(weaponData.boltOpen) + +func PlayBoltCloseAudio(): + var boltClosed = audioInstance2D.instantiate() + add_child(boltClosed) + boltClosed.PlayInstance(weaponData.boltClosed) + +func PlayInsertAudio(): + var insert = audioInstance2D.instantiate() + add_child(insert) + insert.PlayInstance(weaponData.insert) + +func PlayCasingDrop(): + await get_tree().create_timer(casingDelay).timeout; + + var casingDrop = audioInstance2D.instantiate() + add_child(casingDrop) + + if gameData.surface == 0 || gameData.surface == 1 || gameData.surface == 2: + if weaponData.action == 3: + casingDrop.PlayInstance(audioLibrary.shellDropSoft) + else : + casingDrop.PlayInstance(audioLibrary.casingDropSoft) + + elif gameData.surface == 5: + if weaponData.action == 3: + casingDrop.PlayInstance(audioLibrary.shellDropHard) + else : + casingDrop.PlayInstance(audioLibrary.casingDropWood) + + elif gameData.surface == 9: + return + + else : + if weaponData.action == 3: + casingDrop.PlayInstance(audioLibrary.shellDropHard) + else : + casingDrop.PlayInstance(audioLibrary.casingDropHard) + +func FireImpulse(delta): + if fireImpulseTimer < fireImpulse: + gameData.isFiring = true + fireImpulseTimer += delta + else : + gameData.isFiring = false + fireImpulseTimer = 0.0 + fireImpulse = 0.0 + +func InsertFinished(): + gameData.isInserting = false + animator["parameters/conditions/Insert"] = false + +func ReloadFinished(): + gameData.isReloading = false + gameData.isPreparing = false + gameData.isInserting = false + + if weaponData.action == 0 || weaponData.action == 1: + animator["parameters/conditions/Reload_Tactical"] = false + animator["parameters/conditions/Reload_Empty"] = false + elif weaponData.action == 2 || weaponData.action == 3: + animator["parameters/conditions/Reload"] = false + animator["parameters/conditions/Insert_Start"] = false + animator["parameters/conditions/Insert_End"] = false + +func Selector(delta): + if weaponSlot.slotData.firemode == 1: + selectorValue = move_toward(selectorValue, weaponData.semiRotation, delta * weaponData.selectorSpeed) + else : + selectorValue = move_toward(selectorValue, weaponData.autoRotation, delta * weaponData.selectorSpeed) + + if weaponData.selectorDirection == 0: + targetSelectorRotation = Vector3(initialSelectorRotation.x + selectorValue, initialSelectorRotation.y, initialSelectorRotation.z) + elif weaponData.selectorDirection == 1: + targetSelectorRotation = Vector3(initialSelectorRotation.x, initialSelectorRotation.y + selectorValue, initialSelectorRotation.z) + else : + targetSelectorRotation = Vector3(initialSelectorRotation.x, initialSelectorRotation.y, initialSelectorRotation.z + selectorValue) + + skeleton.set_bone_pose_rotation(selectorIndex, Quaternion.from_euler(targetSelectorRotation)) + +func Slide(delta): + var currentPose = skeleton.get_bone_global_pose_no_override(slideIndex) + + if gameData.isFiring || (weaponData.slideLock && weaponSlot.slotData.ammo == 0): + slideValue = lerp(slideValue, weaponData.slideMovement, delta * weaponData.slideSpeed) + else : + slideValue = lerp(slideValue, 0.0, delta * weaponData.slideSpeed) + + if gameData.isReloading: + slideWeight = 0.0 + else : + slideWeight = 1.0 + + if weaponData.slideDirection == weaponData.SlideDirection.X: + var slidePose = currentPose.translated_local(Vector3(slideValue, 0, 0)) + skeleton.set_bone_global_pose_override(slideIndex, slidePose, slideWeight, true) + elif weaponData.slideDirection == weaponData.SlideDirection.Y: + var slidePose = currentPose.translated_local(Vector3(0, slideValue, 0)) + skeleton.set_bone_global_pose_override(slideIndex, slidePose, slideWeight, true) + elif weaponData.slideDirection == weaponData.SlideDirection.Z: + var slidePose = currentPose.translated_local(Vector3(0, 0, slideValue)) + skeleton.set_bone_global_pose_override(slideIndex, slidePose, slideWeight, true) + +func UpdateMuzzlePosition(): + if activeMuzzle != null: + muzzle.position = muzzle.position + Vector3(0, 0, 0.2) + else : + muzzle.position = muzzlePosition + +func UpdateAimOffset(): + if activeOptic != null: + aimOffset = abs(raycast.position.y - activeOptic.position.y) + + if weaponData.foldSights: + skeleton.set_bone_pose_rotation(backSightIndex, Quaternion.from_euler(Vector3(weaponData.foldSightsRotation, 0, 0))) + skeleton.set_bone_pose_rotation(frontSightIndex, Quaternion.from_euler(Vector3(weaponData.foldSightsRotation, 0, 0))) + else : + aimOffset = 0.0 + + if weaponData.foldSights: + skeleton.set_bone_pose_rotation(backSightIndex, Quaternion.from_euler(Vector3(0, 0, 0))) + skeleton.set_bone_pose_rotation(frontSightIndex, Quaternion.from_euler(Vector3(0, 0, 0))) + +func UpdateBarrel(): + if activeBarrel != null: + isUpgraded = true + + for part in defaultParts: + part.hide() + for part in upgradeParts: + part.show() + else : + isUpgraded = false + + for part in defaultParts: + part.show() + for part in upgradeParts: + part.hide() + +func IdleState(): + animator.active = false + gameData.isReloading = false diff --git a/mods/.gitkeep b/mods/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0ff9001 --- /dev/null +++ b/run.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +LOADER_VERSION=1 +INSTALLED_LOADER=0 +if [ -f .LOADERVER ]; then + INSTALLED_LOADER=$(cat .LOADERVER) +fi + +PCK="Public_Demo_2_v2.pck" + +if [ $LOADER_VERSION -ne $INSTALLED_LOADER ]; then + echo Expected loader version $LOADER_VERSION, got $INSTALLED_LOADER, installing... + + echo Creating PCK backup... + if [ -f "$PCK.bak" ]; then + cp "$PCK.bak" "$PCK" + else + cp "$PCK" "$PCK.bak" + fi + echo Done + + echo Adding mod loader... + ./godotpcktool "$PCK" --action add ModLoader.gd + echo $LOADER_VERSION > .LOADERVER + echo Done +fi + +echo Building mods... +GAME_DIR=$(pwd) +for mod in mod_src/* +do + cd "$GAME_DIR/$mod" + mod="$(basename "$mod")" + echo Building $mod... + rm -f "../../mods/$mod.zip" + zip -r "../../mods/$mod.zip" * +done +cd "$GAME_DIR" +echo Done + +echo Running game +./Public_Demo_2_v2.exe --main-pack "$PCK" -s res://ModLoader.gd | tee latest.log \ No newline at end of file