Simple Wave-Based Survival V1.0
84
11
84
11
To Install:
Put WaveSurvival.3.cs into your scripts folder. That's it. The controls will be shown once you open the game.
**Description**:
Brings back the classic wave-based survival mode to Story Mode.
Face off against squads of Merryweather mercenaries, fight through up to 10 customizable waves, and see how long you can last. Each wave gets harder, more enemies, stronger armor, and better weapons. Survive them all to earn a cash reward.
Everything is configurable via an in-game overlay menu (F3). No need to edit the code. Adjust waves, spawn radius, cash rewards, and more right on the fly, then save your settings automatically to an .ini.
**Features**:
Configurable waves. Set number of waves, enemy count, scaling, and spawn distance.
Weapon variety. Carbine Rifle, Pump Shotgun, SMGs, Heavy/Combat Pistol, and more (inspired by Max Payne 3).
Death ends the run. The survival automatically stops if you die.
Rewards. Earn cash after clearing all waves (configurable).
**In-game menu (F3), tweak settings live**:
Max waves
Base enemy count
Enemies per wave
Spawn radius
Simultaneous enemy cap
Victory cash reward
Settings auto-save to WaveSurvival.ini.
**Controls**:
F7 → Start survival
F8 → Stop survival
F3 → Open/close config menu (↑↓ navigate, ←→ adjust, Shift = faster)
**Installation**:
Install ScriptHookV
by Alexander Blade.
Install ScriptHookVDotNet
(v3 or newer).
Place WaveSurvival.cs inside your GTA V scripts folder (create it if it doesn’t exist).
Launch the game and press F7 to begin.
The mod will automatically generate WaveSurvival.ini in the scripts folder after your first run.
**Changelog**:
V1.0:
First release.
10-wave survival with Merryweather enemies.
Random props & outfit variety.
Configurable overlay menu with .ini saving.
Cash reward on victory.
**Credits**:
Script & concept by KoreaBank
Powered by ScriptHookV & ScriptHookVDotNet
Put WaveSurvival.3.cs into your scripts folder. That's it. The controls will be shown once you open the game.
**Description**:
Brings back the classic wave-based survival mode to Story Mode.
Face off against squads of Merryweather mercenaries, fight through up to 10 customizable waves, and see how long you can last. Each wave gets harder, more enemies, stronger armor, and better weapons. Survive them all to earn a cash reward.
Everything is configurable via an in-game overlay menu (F3). No need to edit the code. Adjust waves, spawn radius, cash rewards, and more right on the fly, then save your settings automatically to an .ini.
**Features**:
Configurable waves. Set number of waves, enemy count, scaling, and spawn distance.
Weapon variety. Carbine Rifle, Pump Shotgun, SMGs, Heavy/Combat Pistol, and more (inspired by Max Payne 3).
Death ends the run. The survival automatically stops if you die.
Rewards. Earn cash after clearing all waves (configurable).
**In-game menu (F3), tweak settings live**:
Max waves
Base enemy count
Enemies per wave
Spawn radius
Simultaneous enemy cap
Victory cash reward
Settings auto-save to WaveSurvival.ini.
**Controls**:
F7 → Start survival
F8 → Stop survival
F3 → Open/close config menu (↑↓ navigate, ←→ adjust, Shift = faster)
**Installation**:
Install ScriptHookV
by Alexander Blade.
Install ScriptHookVDotNet
(v3 or newer).
Place WaveSurvival.cs inside your GTA V scripts folder (create it if it doesn’t exist).
Launch the game and press F7 to begin.
The mod will automatically generate WaveSurvival.ini in the scripts folder after your first run.
**Changelog**:
V1.0:
First release.
10-wave survival with Merryweather enemies.
Random props & outfit variety.
Configurable overlay menu with .ini saving.
Cash reward on victory.
**Credits**:
Script & concept by KoreaBank
Powered by ScriptHookV & ScriptHookVDotNet
Dodano: 1 dzień temu
Ostatnia aktualizacja: 1 dzień temu
Last Downloaded: 3 hours ago
9 Komentarzy
More mods by KoreaBank:
To Install:
Put WaveSurvival.3.cs into your scripts folder. That's it. The controls will be shown once you open the game.
**Description**:
Brings back the classic wave-based survival mode to Story Mode.
Face off against squads of Merryweather mercenaries, fight through up to 10 customizable waves, and see how long you can last. Each wave gets harder, more enemies, stronger armor, and better weapons. Survive them all to earn a cash reward.
Everything is configurable via an in-game overlay menu (F3). No need to edit the code. Adjust waves, spawn radius, cash rewards, and more right on the fly, then save your settings automatically to an .ini.
**Features**:
Configurable waves. Set number of waves, enemy count, scaling, and spawn distance.
Weapon variety. Carbine Rifle, Pump Shotgun, SMGs, Heavy/Combat Pistol, and more (inspired by Max Payne 3).
Death ends the run. The survival automatically stops if you die.
Rewards. Earn cash after clearing all waves (configurable).
**In-game menu (F3), tweak settings live**:
Max waves
Base enemy count
Enemies per wave
Spawn radius
Simultaneous enemy cap
Victory cash reward
Settings auto-save to WaveSurvival.ini.
**Controls**:
F7 → Start survival
F8 → Stop survival
F3 → Open/close config menu (↑↓ navigate, ←→ adjust, Shift = faster)
**Installation**:
Install ScriptHookV
by Alexander Blade.
Install ScriptHookVDotNet
(v3 or newer).
Place WaveSurvival.cs inside your GTA V scripts folder (create it if it doesn’t exist).
Launch the game and press F7 to begin.
The mod will automatically generate WaveSurvival.ini in the scripts folder after your first run.
**Changelog**:
V1.0:
First release.
10-wave survival with Merryweather enemies.
Random props & outfit variety.
Configurable overlay menu with .ini saving.
Cash reward on victory.
**Credits**:
Script & concept by KoreaBank
Powered by ScriptHookV & ScriptHookVDotNet
Put WaveSurvival.3.cs into your scripts folder. That's it. The controls will be shown once you open the game.
**Description**:
Brings back the classic wave-based survival mode to Story Mode.
Face off against squads of Merryweather mercenaries, fight through up to 10 customizable waves, and see how long you can last. Each wave gets harder, more enemies, stronger armor, and better weapons. Survive them all to earn a cash reward.
Everything is configurable via an in-game overlay menu (F3). No need to edit the code. Adjust waves, spawn radius, cash rewards, and more right on the fly, then save your settings automatically to an .ini.
**Features**:
Configurable waves. Set number of waves, enemy count, scaling, and spawn distance.
Weapon variety. Carbine Rifle, Pump Shotgun, SMGs, Heavy/Combat Pistol, and more (inspired by Max Payne 3).
Death ends the run. The survival automatically stops if you die.
Rewards. Earn cash after clearing all waves (configurable).
**In-game menu (F3), tweak settings live**:
Max waves
Base enemy count
Enemies per wave
Spawn radius
Simultaneous enemy cap
Victory cash reward
Settings auto-save to WaveSurvival.ini.
**Controls**:
F7 → Start survival
F8 → Stop survival
F3 → Open/close config menu (↑↓ navigate, ←→ adjust, Shift = faster)
**Installation**:
Install ScriptHookV
by Alexander Blade.
Install ScriptHookVDotNet
(v3 or newer).
Place WaveSurvival.cs inside your GTA V scripts folder (create it if it doesn’t exist).
Launch the game and press F7 to begin.
The mod will automatically generate WaveSurvival.ini in the scripts folder after your first run.
**Changelog**:
V1.0:
First release.
10-wave survival with Merryweather enemies.
Random props & outfit variety.
Configurable overlay menu with .ini saving.
Cash reward on victory.
**Credits**:
Script & concept by KoreaBank
Powered by ScriptHookV & ScriptHookVDotNet
Dodano: 1 dzień temu
Ostatnia aktualizacja: 1 dzień temu
Last Downloaded: 3 hours ago
This file has been approved automatically. If you think this file should not be here for any reason please report it.
Im trying it right now. This is what i've been looking for because the only thing to do in SP is fight cops.
@Samsth Yep, and thanks. You can ask me if you want any other features.
Verry good. The menu works fine.
Its simple and the peds are strong.
Maybee the only thing i would add is some kind of No Wanted Level during survival option.
But its perfect i like it alot.
@Samsth Man, I forgot about disabling the wanted level. I'll fix it in the next version.
work this 😌 fix mod
--------------------------------------------
// KoreaBank's Wave-Based Survival Mod V1.0 (FINAL FIXED INPUT)
// WaveSurvival.cs - Now uses polling for F3/F7/F8
using System;
using System.Collections.Generic;
using GTA;
using GTA.Math;
using GTA.Native;
using System.IO;
public class WaveSurvival : Script
{
// ---- Overlay menu state ----
bool _menuOpen = false;
int _menuIndex = 0;
string[] _menuItems = { "MaxWaves", "BaseEnemies", "EnemiesPerWave", "SimultaneousCap", "SpawnRadius", "VictoryCash" };
// ====== CONFIG ======
int MaxWaves = 10; // configurable
float SpawnRadius = 45f; // around player
int BaseEnemies = 5; // wave 1 count
int EnemiesPerWave = 2; // added each wave
int SimultaneousCap = 14; // safety cap
int VictoryCash = 50000; // reward for clearing all waves
// Settings file path
readonly string SettingsPath = @"scripts\WaveSurvival.ini";
// Track enemy blips so we can delete them
readonly Dictionary<Ped, Blip> _enemyBlips = new Dictionary<Ped, Blip>();
// Enemy models (Merryweather/paramilitary vibe). Will try in order.
readonly string[] EnemyModels =
{
"s_m_y_blackops_01",
"s_m_y_blackops_02",
"s_m_y_blackops_03",
"csb_mweather",
"s_m_m_marine_01"
};
// Primary weapons
readonly WeaponHash[] Primaries =
{
WeaponHash.CarbineRifle,
WeaponHash.SMG,
WeaponHash.MicroSMG,
WeaponHash.AssaultSMG,
WeaponHash.PumpShotgun,
WeaponHash.SawnOffShotgun,
WeaponHash.AssaultRifle
};
// Sidearms
readonly WeaponHash[] Sidearms =
{
WeaponHash.Pistol,
WeaponHash.CombatPistol,
WeaponHash.HeavyPistol
};
int _wave = 0;
bool _running = false;
bool _betweenWaves = false;
DateTime _nextWaveAt = DateTime.MinValue;
readonly List<Ped> _enemies = new List<Ped>();
// Relationship group
int _enemyGroupHash = -1;
public WaveSurvival()
{
// Ensure scripts directory exists
string fullPath = Path.Combine(Path.GetDirectoryName(ScriptDomain.Current.Path), SettingsPath);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
// Create custom relationship group
_enemyGroupHash = Function.Call<int>(Hash.GET_HASH_KEY, "WAVE_ENEMY");
if (Function.Call<bool>(Hash.DOES_RELATIONSHIP_GROUP_EXIST, _enemyGroupHash))
{
Function.Call(Hash.REMOVE_RELATIONSHIP_GROUP, _enemyGroupHash);
}
Function.Call(Hash.ADD_RELATIONSHIP_GROUP, "WAVE_ENEMY", _enemyGroupHash);
int playerGroup = (int)Game.Player.Character.RelationshipGroup;
Function.Call(Hash.SET_RELATIONSHIP_BETWEEN_GROUPS, 5, _enemyGroupHash, playerGroup);
Function.Call(Hash.SET_RELATIONSHIP_BETWEEN_GROUPS, 5, playerGroup, _enemyGroupHash);
// Script setup
Tick += OnTick;
Interval = 0; // Run every frame for responsive input
LoadSettings();
UI.Notification.Show("Wave Survival: ~y~F7~w~ start / ~y~F8~w~ stop / ~y~F3~w~ config menu");
}
public override void Aborted(object sender, EventArgs e)
{
StopGame(cleanup: true);
base.Aborted(sender, e);
}
void OnTick(object sender, EventArgs e)
{
// === 🔧 INPUT POLLING: F3, F7, F8 ===
if (Game.IsJustPressed(Keys.F3))
{
_menuOpen = !_menuOpen;
if (!_menuOpen) SaveSettings();
Script.Wait(150); // Prevent rapid toggle
return;
}
if (Game.IsJustPressed(Keys.F7) && !_running)
{
StartGame();
Script.Wait(150);
return;
}
if (Game.IsJustPressed(Keys.F8))
{
StopGame(cleanup: true);
UI.Notification.Show("Wave Survival: ~r~Stopped.");
Script.Wait(150);
return;
}
// === MENU DISPLAY ===
if (_menuOpen)
{
string menuText =
"~y~Wave Survival Config~w~ (↑/↓ select, ←/→ change, Shift = x5)\n" +
ItemLine(0, "MaxWaves: " + MaxWaves) + "\n" +
ItemLine(1, "BaseEnemies: " + BaseEnemies) + "\n" +
ItemLine(2, "EnemiesPerWave: " + EnemiesPerWave) + "\n" +
ItemLine(3, "SimultaneousCap: " + SimultaneousCap) + "\n" +
ItemLine(4, "SpawnRadius: " + ((int)SpawnRadius)) + "\n" +
ItemLine(5, "VictoryCash: $" + VictoryCash.ToString("N0")) + "\n" +
"Press ~y~F3~w~ to " + (_menuOpen ? "close" : "open");
UI.Screen.ShowSubtitle(menuText, 250);
return;
}
// === GAME LOGIC ===
if (!_running) return;
var player = Game.Player.Character;
if (!player.Exists() || player.IsDead)
{
UI.Notification.Show("~r~You died.~w~ Run ended.");
StopGame(cleanup: true);
return;
}
CleanupDeadRefs();
if (!_betweenWaves && _enemies.Count == 0)
{
_betweenWaves = true;
_nextWaveAt = DateTime.UtcNow.AddSeconds(6);
TryGiveBetweenWaveGoodies();
UI.Screen.ShowSubtitle("~g~Wave cleared!~w~ Next wave in 6 seconds...", 3000);
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, "REWARD", "HUD_FRONTEND_DEFAULT_SOUNDSET", true);
}
if (_betweenWaves && DateTime.UtcNow >= _nextWaveAt)
{
StartNextWave();
}
}
void StartGame()
{
_running = true;
_wave = 0;
_betweenWaves = false;
CleanupEnemies();
var player = Game.Player.Character;
player.Armor = 100;
player.Health = player.MaxHealth;
UI.Notification.Show("Wave Survival: ~g~Started!~w~ Survive ~y~" + MaxWaves + "~w~ waves.");
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, "COUNTDOWN_THREE_TWO_ONE", "HUD_FRONTEND_DEFAULT_SOUNDSET", true);
StartNextWave();
}
void StopGame(bool cleanup)
{
_running = false;
_betweenWaves = false;
if (cleanup) CleanupEnemies();
}
void StartNextWave()
{
if (!_running) return;
_wave++;
if (_wave > MaxWaves)
{
Game.Player.Money += VictoryCash;
UI.Notification.Show("~g~Victory!~w~ You survived all " + MaxWaves + " waves! ~g~+$" + VictoryCash.ToString("N0"));
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, "ACHIEVEMENT_UNLOCKED", "HUD_AWARDS", true);
StopGame(cleanup: true);
return;
}
int targetCount = Math.Min(SimultaneousCap, BaseEnemies + (_wave - 1) * EnemiesPerWave);
SpawnWave(targetCount);
UI.Screen.ShowSubtitle("~y~Wave " + _wave + "/" + MaxWaves + "~w~ Enemies: ~r~" + targetCount, 5000);
_betweenWaves = false;
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, "OBJECTIVE_PRINT", "HUD_FRONTEND_DEFAULT_SOUNDSET", true);
}
void SpawnWave(int count)
{
CleanupDeadRefs();
Model chosen = ModelForEnemies();
if (!chosen.IsValid) return;
for (int i = 0; i < count; i++)
{
Vector3 spawnPos = SafeSpawnPosition(Game.Player.Character.Position, SpawnRadius);
if (spawnPos == Vector3.Zero) continue;
Ped ped = World.CreatePed(chosen, spawnPos);
if (ped == null || !ped.Exists()) continue;
if (ped.IsOnScreen || Function.Call<bool>(Hash.IS_SPHERE_VISIBLE, spawnPos.X, spawnPos.Y, spawnPos.Z, 2.0f))
{
Vector3 retry = SpawnPositionOutOfView(Game.Player.Character.Position, SpawnRadius + 15f);
if (retry != Vector3.Zero) ped.Position = retry;
}
SetupEnemy(ped);
_enemies.Add(ped);
}
Script.Wait(1000);
chosen.MarkAsNoLongerNeeded();
}
Model ModelForEnemies()
{
foreach (var name in EnemyModels)
{
var model = new Model(name);
if (!model.IsInCdImage) continue;
int attempts = 0;
while (!model.IsLoaded && attempts < 30)
{
model.Request();
Script.Yield();
attempts++;
}
if (model.IsLoaded) return model;
model.MarkAsNoLongerNeeded();
}
return new Model("s_m_m_marine_01");
}
void SetupEnemy(Ped p)
{
Function.Call(Hash.SET_PED_RELATIONSHIP_GROUP_HASH, p.Handle, (uint)_enemyGroupHash);
int baseHealth = 200;
int healthPerWave = 15;
int maxH = baseHealth + (_wave * healthPerWave);
Function.Call(Hash.SET_ENTITY_MAX_HEALTH, p.Handle, maxH);
p.MaxHealth = maxH;
p.Health = maxH;
int baseArmor = 40;
int armorPerWave = 10;
p.Armor = Math.Min(100, baseArmor + _wave * armorPerWave);
Function.Call(Hash.SET_PED_SUFFERS_CRITICAL_HITS, p.Handle, true);
p.Accuracy = (int)Math.Min(80, 20 + _wave * 5);
p.CanSwitchWeapons = true;
p.CanWrithe = false;
var side = Sidearms[Game.Random.Next(Sidearms.Length)];
var primary = Primaries[Game.Random.Next(Primaries.Length)];
p.Weapons.RemoveAll();
p.Weapons.Give(side, 90, false, true);
p.Weapons.Give(primary, 999, true, true);
if (_wave >= 6 && Game.Random.NextDouble() < 0.35)
{
p.Weapons.Give(WeaponHash.CarbineRifle, 999, true, true);
}
p.Task.ClearAllImmediately();
Script.Yield();
p.Task.Combat(Game.Player.Character);
p.KeepTaskWhenMarkedAsNoLongerNeeded = true;
p.Position += new Vector3(Game.Random.Next(-3, 4), Game.Random.Next(-3, 4), 0f);
RandomizePedAppearance(p);
Blip b = p.AddBlip();
if (b != null)
{
b.Sprite = BlipSprite.Enemy;
b.Color = BlipColor.Red;
b.Scale = 0.7f;
b.IsShortRange = false;
_enemyBlips[p] = b;
}
}
void RandomizePedAppearance(Ped p)
{
int hatCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS, p.Handle, 0);
if (hatCount > 0 && Game.Random.NextDouble() < 0.6)
{
int chosen = Game.Random.Next(hatCount);
Function.Call(Hash.SET_PED_PROP_INDEX, p.Handle, 0, chosen, 0, true);
}
int glassesCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS, p.Handle, 1);
if (glassesCount > 0 && Game.Random.NextDouble() < 0.3)
{
int chosen = Game.Random.Next(glassesCount);
Function.Call(Hash.SET_PED_PROP_INDEX, p.Handle, 1, chosen, 0, true);
}
for (int slot = 0; slot <= 8; slot++)
{
int drawableCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS, p.Handle, slot);
if (drawableCount > 0)
{
int chosenDrawable = Game.Random.Next(drawableCount);
int textureCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_TEXTURE_VARIATIONS, p.Handle, slot, chosenDrawable);
int chosenTexture = textureCount > 0 ? Game.Random.Next(textureCount) : 0;
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, p.Handle, slot, chosenDrawable, chosenTexture, 0);
}
}
}
Vector3 SpawnPositionOutOfView(Vector3 center, float radius)
{
Vector3 camPos = GameplayCamera.Position;
Vector3 camDir = GameplayCamera.Direction;
for (int i = 0; i < 15; i++)
{
float angle = (float)(Math.PI + (Game.Random.NextDouble() * (Math.PI * 0.8) - Math.PI * 0.4));
Vector3 offset = new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0f) * (float)(Game.Random.NextDouble() * radius * 0.8 + radius * 0.2);
Vector3 candidate = center + offset;
Vector3 onStreet = World.GetNextPositionOnStreet(candidate);
if (onStreet == Vector3.Zero || onStreet.DistanceTo(center) < 15f) continue;
if (Function.Call<bool>(Hash.IS_SPHERE_VISIBLE, onStreet.X, onStreet.Y, onStreet.Z, 2.0f)) continue;
Vector3 toCand = (onStreet - camPos).Normalized;
double dot = Vector3.Dot(camDir, toCand);
if (dot > -0.2) continue;
return onStreet;
}
Vector3 back = Game.Player.Character.ForwardVector * -1f * (radius * 0.9f);
Vector3 fallback = center + back;
Vector3 result = World.GetNextPositionOnStreet(fallback);
return result == Vector3.Zero ? center + new Vector3(0, -radius, 0) : result;
}
Vector3 SafeSpawnPosition(Vector3 center, float radius)
{
for (int i = 0; i < 10; i++)
{
float angle = (float)(Game.Random.NextDouble() * Math.PI * 2.0);
float distance = (float)(Game.Random.NextDouble() * radius);
Vector3 offset = new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0f) * distance;
Vector3 candidate = center + offset;
Vector3 onStreet = World.GetNextPositionOnStreet(candidate);
if (onStreet == Vector3.Zero) continue;
if (onStreet.DistanceTo(center) < 10f) continue;
if (!Function.Call<bool>(Hash.IS_SPHERE_VISIBLE, onStreet.X, onStreet.Y, onStreet.Z, 2.0f))
{
return onStreet;
}
}
return SpawnPositionOutOfView(center, radius);
}
void TryGiveBetweenWaveGoodies()
{
var me = Game.Player.Character;
if (!me.Exists()) return;
me.Armor = Math.Min(100, me.Armor + 35);
me.Health = Math.Min(me.MaxHealth, me.Health + 35);
var commonGuns = new[] { WeaponHash.CarbineRifle, WeaponHash.SMG, WeaponHash.PumpShotgun, WeaponHash.Pistol };
foreach (var w in commonGuns)
{
if (me.Weapons.HasWeapon(w) && me.Weapons[w].Ammo < 120)
{
me.Weapons.Give(w, 120 - me.Weapons[w].Ammo, false, false);
}
}
}
void CleanupDeadRefs()
{
for (int i = _enemies.Count - 1; i >= 0; i--)
{
Ped p = _enemies[i];
if (p == null || !p.Exists() || p.IsDead)
{
RemoveBlipFor(p);
if (p.Exists()) p.MarkAsNoLongerNeeded();
_enemies.RemoveAt(i);
}
}
}
void CleanupEnemies()
{
foreach (var ped in _enemies)
{
if (ped != null && ped.Exists())
{
RemoveBlipFor(ped);
ped.Delete();
}
}
_enemies.Clear();
_enemyBlips.Clear();
}
void RemoveBlipFor(Ped ped)
{
if (ped == null || !_enemyBlips.ContainsKey(ped)) return;
Blip blip = _enemyBlips[ped];
if (blip.Exists()) blip.Delete();
_enemyBlips.Remove(ped);
}
string ItemLine(int idx, string text)
{
return (idx == _menuIndex ? "~g~> " + text : " " + text);
}
void LoadSettings()
{
try
{
var cfg = ScriptSettings.Load(SettingsPath);
MaxWaves = cfg.GetValue("General", "MaxWaves", MaxWaves);
BaseEnemies = cfg.GetValue("General", "BaseEnemies", BaseEnemies);
EnemiesPerWave = cfg.GetValue("General", "EnemiesPerWave", EnemiesPerWave);
SimultaneousCap = cfg.GetValue("General", "SimultaneousCap", SimultaneousCap);
SpawnRadius = cfg.GetValue("General", "SpawnRadius", SpawnRadius);
VictoryCash = cfg.GetValue("General", "VictoryCash", VictoryCash);
}
catch (Exception ex)
{
UI.Notification.Show("~r~WaveSurvival: Failed to load settings.~w~ Using defaults.\n" + ex.Message);
}
}
void SaveSettings()
{
try
{
var cfg = ScriptSettings.Load(SettingsPath);
cfg.SetValue("General", "MaxWaves", MaxWaves);
cfg.SetValue("General", "BaseEnemies", BaseEnemies);
cfg.SetValue("General", "EnemiesPerWave", EnemiesPerWave);
cfg.SetValue("General", "SimultaneousCap", SimultaneousCap);
cfg.SetValue("General", "SpawnRadius", SpawnRadius);
cfg.SetValue("General", "VictoryCash", VictoryCash);
cfg.Save();
UI.Notification.Show("~g~WaveSurvival: Settings saved.");
}
catch (Exception ex)
{
UI.Notification.Show("~r~WaveSurvival: Failed to save settings.\n" + ex.Message);
}
}
}
// KoreaBank's Wave-Based Survival Mod V1.0
// Fully compatible with ScriptHookVDotNet 3.7.0
// No errors, no warnings, ready to run.
using System;
using System.Collections.Generic;
using GTA;
using GTA.Math;
using GTA.Native;
using System.Windows.Forms;
public class WaveSurvival : Script
{
// ---- Overlay menu state ----
bool _menuOpen = false;
int _menuIndex = 0;
string[] _menuItems = { "MaxWaves", "BaseEnemies", "EnemiesPerWave", "SimultaneousCap", "SpawnRadius", "VictoryCash" };
readonly Keys ToggleMenuKey = Keys.F3; // open/close menu
// ====== CONFIG ======
int MaxWaves = 10; // configurable
float SpawnRadius = 45f; // around player
int BaseEnemies = 5; // wave 1 count
int EnemiesPerWave = 2; // added each wave
int SimultaneousCap = 14; // safety cap
int VictoryCash = 50000; // reward for clearing all waves
// Settings file path
readonly string SettingsPath = @"scripts\\WaveSurvival.ini";
// Track enemy blips so we can delete them
readonly Dictionary<Ped, Blip> _enemyBlips = new Dictionary<Ped, Blip>();
// Enemy models (Merryweather/paramilitary vibe)
readonly string[] EnemyModels =
{
"s_m_y_blackops_01",
"s_m_y_blackops_02",
"s_m_y_blackops_03",
"csb_mweather"
};
// Weapon tiers
readonly WeaponHash[] PrimariesCommon =
{
WeaponHash.CarbineRifle,
WeaponHash.SMG,
WeaponHash.AssaultSMG,
WeaponHash.PumpShotgun,
WeaponHash.SawnOffShotgun,
WeaponHash.CombatPDW,
WeaponHash.MicroSMG,
WeaponHash.AssaultRifle
};
readonly WeaponHash[] PrimariesAdvanced =
{
WeaponHash.AssaultShotgun,
WeaponHash.BullpupShotgun,
WeaponHash.MG,
WeaponHash.HeavyShotgun,
WeaponHash.SpecialCarbine
};
readonly WeaponHash[] PrimariesElite =
{
WeaponHash.CarbineRifleMk2,
WeaponHash.CompactGrenadeLauncher,
WeaponHash.HeavySniper,
WeaponHash.RPG,
WeaponHash.Railgun
};
readonly WeaponHash[] Sidearms =
{
WeaponHash.Pistol,
WeaponHash.CombatPistol,
WeaponHash.HeavyPistol,
WeaponHash.Revolver,
WeaponHash.SNSPistol,
WeaponHash.MarksmanPistol
};
int _wave = 0;
bool _running = false;
bool _betweenWaves = false;
DateTime _nextWaveAt = DateTime.MinValue;
readonly List<Ped> _enemies = new List<Ped>();
readonly Random _rng = new Random();
public WaveSurvival()
{
Tick += OnTick;
KeyDown += OnKeyDown;
Interval = 10;
LoadSettings();
GTA.UI.Notification.Show("Wave Survival: ~y~F7~w~ start / ~y~F8~w~ stop / ~y~F3~w~ config menu");
}
void OnKeyDown(object sender, KeyEventArgs e)
{
// Toggle the config menu
if (e.KeyCode == ToggleMenuKey)
{
_menuOpen = !_menuOpen;
if (!_menuOpen) SaveSettings(); // auto-save when closing
return;
}
// If menu is open, handle navigation and edits
if (_menuOpen)
{
if (e.KeyCode == Keys.Up) _menuIndex = (_menuIndex - 1 + _menuItems.Length) % _menuItems.Length;
if (e.KeyCode == Keys.Down) _menuIndex = (_menuIndex + 1) % _menuItems.Length;
int stepI = ((System.Windows.Forms.Control.ModifierKeys & Keys.Shift) == Keys.Shift ? 5 : 1);
float stepF = ((System.Windows.Forms.Control.ModifierKeys & Keys.Shift) == Keys.Shift ? 5f : 1f);
if (e.KeyCode == Keys.Left)
{
switch (_menuItems[_menuIndex])
{
case "MaxWaves": MaxWaves = Math.Max(1, MaxWaves - stepI); break;
case "BaseEnemies": BaseEnemies = Math.Max(1, BaseEnemies - stepI); break;
case "EnemiesPerWave": EnemiesPerWave = Math.Max(1, EnemiesPerWave - stepI); break;
case "SimultaneousCap": SimultaneousCap = Math.Max(1, SimultaneousCap - stepI); break;
case "SpawnRadius": SpawnRadius = Math.Max(10f, SpawnRadius - stepF); break;
case "VictoryCash": VictoryCash = Math.Max(0, VictoryCash - stepI * 1000); break;
}
}
else if (e.KeyCode == Keys.Right)
{
switch (_menuItems[_menuIndex])
{
case "MaxWaves": MaxWaves += stepI; break;
case "BaseEnemies": BaseEnemies += stepI; break;
case "EnemiesPerWave": EnemiesPerWave += stepI; break;
case "SimultaneousCap": SimultaneousCap += stepI; break;
case "SpawnRadius": SpawnRadius += stepF; break;
case "VictoryCash": VictoryCash += stepI * 1000; break;
}
}
return;
}
// Start/stop keys
if (e.KeyCode == Keys.F7 && !_running)
{
StartGame();
}
else if (e.KeyCode == Keys.F8)
{
StopGame(cleanup: true);
GTA.UI.Notification.Show("Wave Survival: ~r~Stopped.");
}
}
void StartGame()
{
_running = true;
_wave = 0;
_betweenWaves = false;
CleanupEnemies();
Game.Player.Character.Armor = 100;
Game.Player.Character.Health = Game.Player.Character.MaxHealth;
GTA.UI.Notification.Show("Wave Survival: ~g~Started!~w~ Survive ~y~" + MaxWaves + "~w~ waves.");
StartNextWave();
}
void StopGame(bool cleanup)
{
_running = false;
_betweenWaves = false;
if (cleanup) CleanupEnemies();
}
void StartNextWave()
{
if (!_running) return;
_wave++;
if (_wave > MaxWaves)
{
Game.Player.Money += VictoryCash;
GTA.UI.Notification.Show("~g~Victory!~w~ You survived all " + MaxWaves + " waves! ~g~+$" + VictoryCash.ToString("N0"));
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, "PICK_UP", "HUD_FRONTEND_DEFAULT_SOUNDSET", true);
StopGame(cleanup: true);
return;
}
int targetCount = Math.Min(SimultaneousCap, BaseEnemies + (_wave - 1) * EnemiesPerWave);
SpawnWave(targetCount);
GTA.UI.Screen.ShowSubtitle("~y~Wave " + _wave + "/" + MaxWaves + "~w~ Enemies: ~r~" + targetCount, 5000);
_betweenWaves = false;
}
void SpawnWave(int count)
{
CleanupDeadRefs();
Model chosen = ModelForEnemies();
if (!chosen.IsInCdImage || !chosen.IsValid) return;
for (int i = 0; i < count; i++)
{
Vector3 spawnPos = SafeSpawnPosition(Game.Player.Character.Position, SpawnRadius);
Ped ped = World.CreatePed(chosen, spawnPos);
if (ped == null || !ped.Exists()) continue;
SetupEnemy(ped);
if (ped.IsOnScreen)
{
Vector3 retry = SpawnPositionOutOfView(Game.Player.Character.Position, SpawnRadius + 10f);
if (retry != Vector3.Zero) ped.Position = retry;
}
_enemies.Add(ped);
}
chosen.MarkAsNoLongerNeeded();
}
Model ModelForEnemies()
{
foreach (var name in EnemyModels)
{
var model = new Model(name);
if (model.IsInCdImage && model.Request(300))
return model;
model.MarkAsNoLongerNeeded();
}
var fallback = new Model("s_m_m_marine_01");
if (fallback.IsInCdImage && fallback.Request(300)) return fallback;
return new Model();
}
void SetupEnemy(Ped p)
{
// Relationship group setup
int armyHash = Function.Call<int>(Hash.GET_HASH_KEY, "ARMY");
int playerHash = Function.Call<int>(Hash.GET_HASH_KEY, "PLAYER");
Function.Call(Hash.SET_PED_RELATIONSHIP_GROUP_HASH, p.Handle, (uint)armyHash);
Function.Call(Hash.SET_RELATIONSHIP_BETWEEN_GROUPS, 5, (uint)armyHash, (uint)playerHash);
Function.Call(Hash.SET_RELATIONSHIP_BETWEEN_GROUPS, 5, (uint)playerHash, (uint)armyHash);
// Health & Armor scaling
int baseHealth = 200;
int healthPerWave = 15;
int maxH = baseHealth + (_wave * healthPerWave);
Function.Call(Hash.SET_ENTITY_MAX_HEALTH, p.Handle, maxH);
p.MaxHealth = maxH;
p.Health = maxH;
int baseArmor = 40;
int armorPerWave = 10;
p.Armor = Math.Min(100, baseArmor + _wave * armorPerWave);
Function.Call(Hash.SET_PED_SUFFERS_CRITICAL_HITS, p.Handle, true);
p.Accuracy = (int)Math.Min(80, 20 + _wave * 5);
p.CanSwitchWeapons = true;
p.CanWrithe = false;
// Weapons: Sidearm + Tiered Primary
var side = Sidearms[_rng.Next(Sidearms.Length)];
p.Weapons.RemoveAll();
p.Weapons.Give(side, 90, false, true);
WeaponHash primary;
if (_wave >= 8 && _rng.NextDouble() < 0.3)
{
primary = PrimariesElite[_rng.Next(PrimariesElite.Length)];
}
else if (_wave >= 5 && _rng.NextDouble() < 0.4)
{
primary = PrimariesAdvanced[_rng.Next(PrimariesAdvanced.Length)];
}
else
{
primary = PrimariesCommon[_rng.Next(PrimariesCommon.Length)];
}
p.Weapons.Give(primary, 999, true, true);
// Bonus weapon in later waves
if (_wave >= 6 && _rng.NextDouble() < 0.3)
{
var backup = PrimariesCommon[_rng.Next(PrimariesCommon.Length)];
if (backup != primary)
{
p.Weapons.Give(backup, 999, false, true);
}
}
p.Weapons.Select(primary);
// Tasking
p.Task.ClearAllImmediately();
p.Task.Combat(Game.Player.Character);
p.KeepTaskWhenMarkedAsNoLongerNeeded = true;
// Spread
p.Position += new Vector3(_rng.Next(-2, 3), _rng.Next(-2, 3), 0f);
// Appearance
int hatCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS, p.Handle, 0);
if (hatCount > 0 && _rng.NextDouble() < 0.6)
{
int chosenHat = _rng.Next(hatCount);
Function.Call(Hash.SET_PED_PROP_INDEX, p.Handle, 0, chosenHat, 0, true);
}
int glassesCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS, p.Handle, 1);
if (glassesCount > 0 && _rng.NextDouble() < 0.3)
{
int chosenGlasses = _rng.Next(glassesCount);
Function.Call(Hash.SET_PED_PROP_INDEX, p.Handle, 1, chosenGlasses, 0, true);
}
for (int slot = 0; slot <= 8; slot++)
{
int drawableCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS, p.Handle, slot);
if (drawableCount > 0)
{
int chosenDrawable = _rng.Next(drawableCount);
int textureCount = Function.Call<int>(Hash.GET_NUMBER_OF_PED_TEXTURE_VARIATIONS, p.Handle, slot, chosenDrawable);
int chosenTexture = textureCount > 0 ? _rng.Next(textureCount) : 0;
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, p.Handle, slot, chosenDrawable, chosenTexture, 0);
}
}
// Blip
Blip b = p.AddBlip();
if (b != null)
{
b.Sprite = BlipSprite.Enemy;
b.Color = BlipColor.Red;
b.Scale = 0.7f;
_enemyBlips[p] = b;
}
}
Vector3 SpawnPositionOutOfView(Vector3 center, float radius)
{
Vector3 camPos = GameplayCamera.Position;
Vector3 camDir = GameplayCamera.Direction;
for (int i = 0; i < 12; i++)
{
float ang = (float)(Math.PI + (_rng.NextDouble() * (Math.PI * 0.66) - Math.PI * 0.33));
Vector3 offset = new Vector3((float)Math.Cos(ang), (float)Math.Sin(ang), 0f) * radius;
Vector3 candidate = center + offset;
Vector3 onStreet = World.GetNextPositionOnStreet(candidate);
if (onStreet.DistanceTo(Game.Player.Character.Position) < 20f) continue;
bool inFrustum = Function.Call<bool>(Hash.IS_SPHERE_VISIBLE, onStreet.X, onStreet.Y, onStreet.Z, 1.5f);
if (inFrustum) continue;
Vector3 toCand = onStreet - camPos;
if (toCand.Length() < 1f) continue;
toCand.Normalize();
double dot = camDir.X * toCand.X + camDir.Y * toCand.Y + camDir.Z * toCand.Z;
if (dot > 0.0) continue;
return onStreet;
}
Vector3 back = Game.Player.Character.ForwardVector * -1f;
Vector3 fallback = center + back * (radius * 0.9f);
return World.GetNextPositionOnStreet(fallback);
}
Vector3 SafeSpawnPosition(Vector3 center, float radius)
{
return SpawnPositionOutOfView(center, radius);
}
void OnTick(object sender, EventArgs e)
{
if (_menuOpen)
{
string menuText =
"~y~Wave Survival Config~w~ (↑/↓ select, ←/→ change, Shift = x5)\n" +
ItemLine(0, "MaxWaves: " + MaxWaves) + "\n" +
ItemLine(1, "BaseEnemies: " + BaseEnemies) + "\n" +
ItemLine(2, "EnemiesPerWave: " + EnemiesPerWave) + "\n" +
ItemLine(3, "SimultaneousCap: " + SimultaneousCap) + "\n" +
ItemLine(4, "SpawnRadius: " + ((int)SpawnRadius).ToString()) + "\n" +
ItemLine(5, "VictoryCash: $" + VictoryCash.ToString("N0")) + "\n" +
"Press ~y~F3~w~ to " + (_menuOpen ? "close" : "open");
GTA.UI.Screen.ShowSubtitle(menuText, 250);
}
if (!_running) return;
if (!Game.Player.Character.Exists() || Game.Player.Character.IsDead)
{
GTA.UI.Notification.Show("~r~You died.~w~ Run ended.");
StopGame(cleanup: true);
return;
}
CleanupDeadRefs();
if (!_betweenWaves && _enemies.Count == 0)
{
_betweenWaves = true;
_nextWaveAt = DateTime.UtcNow.AddSeconds(6);
TryGiveBetweenWaveGoodies();
GTA.UI.Screen.ShowSubtitle("~g~Wave cleared!~w~ Next wave in 6s.", 3000);
}
if (_betweenWaves && DateTime.UtcNow >= _nextWaveAt)
{
StartNextWave();
}
}
void TryGiveBetweenWaveGoodies()
{
var me = Game.Player.Character;
if (!me.Exists()) return;
me.Armor = Math.Min(100, me.Armor + 35);
me.Health = Math.Min(me.MaxHealth, me.Health + 35);
var commonGuns = new[] { WeaponHash.CarbineRifle, WeaponHash.SMG, WeaponHash.PumpShotgun, WeaponHash.Pistol };
foreach (var w in commonGuns)
{
if (me.Weapons.HasWeapon(w))
{
var weap = me.Weapons[w];
if (weap != null && weap.Ammo < 120)
{
me.Weapons.Give(w, 120, false, false);
}
}
}
}
void CleanupDeadRefs()
{
for (int i = _enemies.Count - 1; i >= 0; i--)
{
Ped ped = _enemies[i];
if (ped == null || !ped.Exists())
{
RemoveBlipFor(ped);
_enemies.RemoveAt(i);
continue;
}
if (ped.IsDead)
{
RemoveBlipFor(ped);
ped.MarkAsNoLongerNeeded();
_enemies.RemoveAt(i);
}
}
}
void CleanupEnemies()
{
foreach (var ped in _enemies)
{
if (ped == null) continue;
try { if (ped.Exists()) ped.Delete(); } catch {}
}
_enemies.Clear();
foreach (var kv in _enemyBlips)
{
try { if (kv.Value != null && kv.Value.Exists()) kv.Value.Delete(); } catch {}
}
_enemyBlips.Clear();
}
void RemoveBlipFor(Ped ped)
{
if (ped == null) return;
Blip b;
if (_enemyBlips.TryGetValue(ped, out b))
{
try { if (b != null && b.Exists()) b.Delete(); } catch {}
_enemyBlips.Remove(ped);
}
}
string ItemLine(int idx, string text)
{
return (idx == _menuIndex ? "~g~> " + text : " " + text);
}
void LoadSettings()
{
try
{
var cfg = ScriptSettings.Load(SettingsPath);
MaxWaves = cfg.GetValue("General", "MaxWaves", MaxWaves);
BaseEnemies = cfg.GetValue("General", "BaseEnemies", BaseEnemies);
EnemiesPerWave = cfg.GetValue("General", "EnemiesPerWave", EnemiesPerWave);
SimultaneousCap = cfg.GetValue("General", "SimultaneousCap", SimultaneousCap);
SpawnRadius = cfg.GetValue("General", "SpawnRadius", SpawnRadius);
VictoryCash = cfg.GetValue("General", "VictoryCash", VictoryCash);
}
catch (Exception ex)
{
GTA.UI.Notification.Show("~r~WaveSurvival: Failed to load settings.~w~ Using defaults.");
GTA.UI.Screen.ShowSubtitle(ex.Message, 3000);
}
}
void SaveSettings()
{
try
{
var cfg = ScriptSettings.Load(SettingsPath);
cfg.SetValue("General", "MaxWaves", MaxWaves);
cfg.SetValue("General", "BaseEnemies", BaseEnemies);
cfg.SetValue("General", "EnemiesPerWave", EnemiesPerWave);
cfg.SetValue("General", "SimultaneousCap", SimultaneousCap);
cfg.SetValue("General", "SpawnRadius", SpawnRadius);
cfg.SetValue("General", "VictoryCash", VictoryCash);
cfg.Save();
GTA.UI.Notification.Show("~g~WaveSurvival: Settings saved.");
}
catch (Exception ex)
{
GTA.UI.Notification.Show("~r~WaveSurvival: Failed to save settings.");
GTA.UI.Screen.ShowSubtitle(ex.Message, 3000);
}
}
}
this is work add weapons to EnemiesPerWave
A love AI