├── .gitignore ├── README.md ├── gamedata └── workshopdefaultmap.json └── src ├── .gitignore ├── ForceFullUpdate.cs ├── WorkshopDefaultMap.cs └── WorkshopDefaultMap.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /obj 3 | *.sln -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cs2_WorkshopDefaultMap 2 | A CSSharp plugin to set default map after server restart till valve fixes it. 3 | 4 | Configuration: 5 | 1) Place the latest release in your plugins folder and (re)start the server. This is to generate config file in `addons/counterstrikesharp/configs/WorkshopDefaultMap/WorkshopDefaultMap.json`. 6 | 2) Open the above json file in your favourite text editor and add your workshop mapname or workshop mapid in `Map` or even some default valve map. 7 | 3) Restart the server. 8 | 9 | > [!Caution] 10 | > • If you added workshop mapname in the config file, make sure the map is in some workshop collection and you must have `host_workshop_collection` in your server startup line. 11 | 12 | > [!Caution] 13 | > • If you added workshop mapid in the config file, it isn't compulsory to add `host_workshop_collection` in your server startup line ~~but server might change the map to that specific map twice in this case (This will be the case only till CSSharp exposes `Server.WorkshopID` variable)~~ 14 | -------------------------------------------------------------------------------- /gamedata/workshopdefaultmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "INetworkServerService_GetIGameServer": { 3 | "offsets": { 4 | "windows": 23, 5 | "linux": 24 6 | } 7 | }, 8 | "INetworkGameServer_Slots": { 9 | "offsets": { 10 | "windows": 624, 11 | "linux": 640 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /obj 3 | *.sln -------------------------------------------------------------------------------- /src/ForceFullUpdate.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using CounterStrikeSharp.API; 3 | using CounterStrikeSharp.API.Core; 4 | using CounterStrikeSharp.API.Modules.Memory; 5 | 6 | 7 | [StructLayout(LayoutKind.Sequential)] 8 | struct CUtlMemory 9 | { 10 | public unsafe nint* m_pMemory; 11 | public int m_nAllocationCount; 12 | public int m_nGrowSize; 13 | } 14 | 15 | [StructLayout(LayoutKind.Sequential)] 16 | struct CUtlVector 17 | { 18 | public unsafe nint this[int index] 19 | { 20 | get => this.m_Memory.m_pMemory[index]; 21 | set => this.m_Memory.m_pMemory[index] = value; 22 | } 23 | 24 | public int m_iSize; 25 | public CUtlMemory m_Memory; 26 | 27 | public nint Element(int index) => this[index]; 28 | } 29 | 30 | class INetworkServerService : NativeObject 31 | { 32 | private readonly VirtualFunctionWithReturn GetIGameServerFunc; 33 | 34 | public INetworkServerService() : base(NativeAPI.GetValveInterface(0, "NetworkServerService_001")) 35 | { 36 | this.GetIGameServerFunc = new VirtualFunctionWithReturn(this.Handle, GameData.GetOffset("INetworkServerService_GetIGameServer")); 37 | } 38 | 39 | public INetworkGameServer GetIGameServer() 40 | { 41 | return new INetworkGameServer(this.GetIGameServerFunc.Invoke(this.Handle)); 42 | } 43 | } 44 | 45 | public class INetworkGameServer : NativeObject 46 | { 47 | private static int SlotsOffset = GameData.GetOffset("INetworkGameServer_Slots"); 48 | 49 | private CUtlVector Slots; 50 | 51 | public INetworkGameServer(nint ptr) : base(ptr) 52 | { 53 | this.Slots = Marshal.PtrToStructure(base.Handle + SlotsOffset); 54 | } 55 | } -------------------------------------------------------------------------------- /src/WorkshopDefaultMap.cs: -------------------------------------------------------------------------------- 1 | namespace WorkshopDefaultMap; 2 | 3 | using CounterStrikeSharp.API; 4 | using CounterStrikeSharp.API.Core; 5 | using CounterStrikeSharp.API.Modules.Timers; 6 | using Microsoft.Extensions.Logging; 7 | using System.Runtime.InteropServices; 8 | using System.Text.Json.Serialization; 9 | 10 | public class WorkshopDefaultMapConfig : BasePluginConfig 11 | { 12 | public override int Version { get; set; } = 1; 13 | 14 | [JsonPropertyName("Map")] 15 | public string Map { get; set; } = "Awp_arena_vlgbeta_gg2"; 16 | } 17 | 18 | public class WorkshopDefaultMap : BasePlugin, IPluginConfig 19 | { 20 | public override string ModuleName => "Workshop Collection Default Map"; 21 | public override string ModuleVersion => "0.5"; 22 | public override string ModuleAuthor => "Cruze"; 23 | public override string ModuleDescription => "Sets default map after server restart"; 24 | 25 | private bool g_bServerStarted = true; 26 | // private ulong g_uOldMapId; 27 | 28 | private Timer? g_TimerForceReset = null; 29 | private Timer? g_TimerChangeMap = null; 30 | 31 | public required WorkshopDefaultMapConfig Config { get; set; } = new(); 32 | 33 | public void OnConfigParsed(WorkshopDefaultMapConfig config) 34 | { 35 | Config = config; 36 | 37 | if(string.IsNullOrEmpty(Config.Map)) 38 | Logger.LogError("Map specified in config is blank. Plugin will not work as intended."); 39 | } 40 | 41 | public override void Load(bool hotReload) 42 | { 43 | base.Load(hotReload); 44 | 45 | RegisterListener(OnMapStart); 46 | } 47 | 48 | public override void Unload(bool hotReload) 49 | { 50 | base.Unload(hotReload); 51 | 52 | RemoveListener(OnMapStart); 53 | } 54 | 55 | 56 | private void OnMapStart(string mapName) 57 | { 58 | if(string.IsNullOrEmpty(Config.Map)) return; 59 | 60 | if(g_bServerStarted || g_TimerForceReset != null) 61 | { 62 | g_bServerStarted = false; 63 | g_TimerForceReset?.Kill(); 64 | g_TimerForceReset = AddTimer(10.0f, ResetTimer); 65 | 66 | g_TimerChangeMap?.Kill(); 67 | g_TimerChangeMap = AddTimer(5.0f, ChangeMap); 68 | } 69 | } 70 | 71 | private void ChangeMap() 72 | { 73 | g_TimerChangeMap = null; 74 | 75 | if(string.IsNullOrEmpty(Config.Map)) return; 76 | 77 | if(Server.MapName.Equals(Config.Map, StringComparison.OrdinalIgnoreCase)) return; 78 | 79 | if(IsDefaultMap(Server.MapName)) 80 | { 81 | Server.ExecuteCommand($"changelevel {Config.Map}"); 82 | Logger.LogInformation($"Changed map to {Config.Map}."); 83 | return; 84 | } 85 | 86 | if(!ulong.TryParse(Config.Map, out ulong mapid)) 87 | { 88 | Server.ExecuteCommand($"ds_workshop_changelevel {Config.Map}"); 89 | Logger.LogInformation($"Changed map to {Config.Map}."); 90 | return; 91 | } 92 | 93 | if(GetCurrentWorkshopMapID() == mapid) return; 94 | 95 | // if(g_uOldMapId == mapid) return; // Hacky fix till there is a way to find workshop id of a map. 96 | 97 | Server.ExecuteCommand($"host_workshop_map {mapid}"); 98 | Logger.LogInformation($"Changed map to mapid {mapid}."); 99 | // g_uOldMapId = mapid; 100 | } 101 | 102 | private void ResetTimer() 103 | { 104 | g_TimerForceReset = null; 105 | g_TimerChangeMap?.Kill(); 106 | g_TimerChangeMap = null; 107 | } 108 | 109 | private bool IsDefaultMap(string map) 110 | { 111 | List defaultMapPool = ["ar_baggage","ar_pool_day","ar_shoots","de_anubis","de_ancient","de_dust2","de_inferno", 112 | "de_mirage","de_nuke","de_overpass","de_vertigo","de_basalt","de_palais","de_train", 113 | "de_whistle","cs_italy","cs_office"]; 114 | 115 | return defaultMapPool.Contains(map); 116 | } 117 | 118 | private delegate IntPtr GetAddonNameDelegate(IntPtr thisPtr); 119 | private readonly INetworkServerService networkServerService = new(); 120 | public ulong GetCurrentWorkshopMapID() 121 | { 122 | IntPtr networkGameServer = networkServerService.GetIGameServer().Handle; 123 | IntPtr vtablePtr = Marshal.ReadIntPtr(networkGameServer); 124 | IntPtr functionPtr = Marshal.ReadIntPtr(vtablePtr + (25 * IntPtr.Size)); 125 | var getAddonName = Marshal.GetDelegateForFunctionPointer(functionPtr); 126 | IntPtr result = getAddonName(networkGameServer); 127 | return ulong.Parse(Marshal.PtrToStringAnsi(result)!.Split(',')[0]); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/WorkshopDefaultMap.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------