Build/editor stuff and fix mod compilation

This commit is contained in:
2026-03-11 19:31:41 +01:00
parent 9b5cea7372
commit 2b72360a9b
6 changed files with 322 additions and 276 deletions

96
.nvim.lua Normal file
View File

@@ -0,0 +1,96 @@
require("lua/lemon")
require("lua/snippets")
vim.opt.errorformat = table.concat({
-- '%f(%l\\,%c):\\ %tarning\\ %m\\ [%.%#]',
'%f(%l\\,%c):\\ %trror\\ %m\\ [%.%#]',
}, ',')
local def_workspace = { args = {}, build_type = "Debug", binary = "ng", build_target = "native", }
local workspace = InitWorkspace(def_workspace)
local bin_target
local bin_name
local function updateBuildEnv()
-- The run (F6) arguments
vim.opt.makeprg = "dotnet build"
end
updateBuildEnv()
-- Update args for both run and debug configs
local function updateArgs(args)
workspace.args = args
WriteWorkspace()
end
local function updateTarget(tgt)
if type(tgt) ~= "string" then
vim.api.nvim_echo({ { "Invalid target", "ErrorMsg" } }, false, {})
return
end
workspace.binary = tgt
updateBuildEnv()
WriteWorkspace()
end
-- The Configure command
vim.api.nvim_create_user_command("Configure", function(a)
local build_type = "Debug"
local build_target = "native"
local a1 = a.fargs
if #a1 > 0 then build_type = a1[1] end
if #a1 > 1 then build_target = a1[2] end
vim.print(a1[1])
workspace.build_type = build_type
workspace.build_target = build_target
updateBuildEnv()
WriteWorkspace()
local args = {}
table.insert(args, "-B" .. build_folder)
table.insert(args, "-GNinja")
table.insert(args, "-DCMAKE_BUILD_TYPE=" .. build_type)
table.insert(args, "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON")
if workspace.build_target == 'web' then
table.insert(args, "-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/Web.cmake")
table.insert(args, "-DTOOLS_HINT_PATH=" .. build_folder .. "/../b_Tool")
else
table.insert(args, "-DCMAKE_CXX_COMPILER=clang++")
table.insert(args, "-DCMAKE_C_COMPILER=clang")
end
-- Feed into cmd !cmake
vim.fn.feedkeys(":!cmake " .. table.concat(args, " "))
end, { nargs = '*', desc = "Update run/debug arguments" })
vim.api.nvim_create_user_command("Target", function(a) updateTarget(a.args) end,
{ nargs = 1, desc = "Update run/debug target" })
vim.api.nvim_create_user_command("Args", function(a) updateArgs(a.fargs) end,
{ nargs = '*', desc = "Update run/debug arguments" })
vim.api.nvim_create_user_command("ReloadWorkspace", function(a)
workspace = InitWorkspace(def_workspace)
updateBuildEnv()
end,
{ nargs = 0, desc = "Reload workspace debug/run configuration from file" })
-- F6 to run the application
local function buildAndRun(silent)
SaveAllCode()
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true)
vim.schedule(function()
MakeAnd(function() end, silent)
end)
end
local function buildAndRunSilent() buildAndRun(true) end
vim.keymap.set('n', '<F7>', buildAndRunSilent)
vim.keymap.set('i', '<F7>', buildAndRunSilent)
vim.keymap.set('n', '<F19>', buildAndRun)
vim.keymap.set('i', '<F19>', buildAndRun)
vim.keymap.set('n', '<S-F7>', buildAndRun)
vim.keymap.set('i', '<S-F7>', buildAndRun)

192
lua/lemon.lua Normal file
View File

@@ -0,0 +1,192 @@
Lemon = {
ws = {},
ws_file = '.nvim.workspace.lua',
term_buf = nil,
term_win_cmd = 'belowright 12split',
}
function LoadWorkspace()
-- Load persistent configuration from .workspace.lua
local loaded, workspace = pcall(dofile, Lemon.ws_file)
if not loaded then return nil end
return workspace
end
function WriteWorkspace()
-- A very minimal serializer for workspace configuration
local s = { l = "", ls = {}, i = "" }
local function w(v) s.l = s.l .. v end
local function nl()
s.ls[#s.ls + 1] = s.l; s.l = s.i;
end
local function wv(v)
local t = type(v)
if t == 'table' then
w('{'); local pi = s.i; s.i = s.i .. " "
for k1, v1 in pairs(v) do
nl(); w('['); wv(k1); w('] = '); wv(v1); w(',')
end
s.i = pi; nl(); w('}');
elseif t == 'number' then
w(tostring(v))
elseif t == 'string' then
w('"' .. v .. '"')
else
w(tostring(v))
end
end
-- Write the workspace file
w("return "); wv(Lemon.ws); nl()
vim.fn.writefile(s.ls, Lemon.ws_file)
end
-- Loads the workspace from the file, or return the default
---@param default table
---@return table
function InitWorkspace(default)
Lemon.ws = LoadWorkspace()
if Lemon.ws == nil then
Lemon.ws = default
end
return Lemon.ws
end
function TermShow()
local info = GetTermInfo()
if info == nil then
-- Create new terminal buffer
vim.cmd(Lemon.term_win_cmd)
vim.cmd('terminal')
Lemon.term_buf = vim.api.nvim_get_current_buf()
-- Mark buffer so we can identify it later
vim.api.nvim_buf_set_var(Lemon.term_buf, 'lemon_terminal', true)
info = GetTermInfo()
elseif info.win == nil then
-- Buffer exists but not visible, open it
vim.cmd(Lemon.term_win_cmd)
vim.api.nvim_win_set_buf(0, Lemon.term_buf)
else
-- Window is visible, switch to it
vim.api.nvim_set_current_win(info.win)
end
return info
end
-- Find or create persistent terminal buffer, open window, and run command
function TermRun(cmd)
local info = TermShow()
-- Send command to terminal
vim.fn.chansend(info.job_id, '\021' .. cmd .. '\n')
vim.fn.feedkeys("G", "n")
end
-- Get terminal buffer and job_id if valid, returns {buf, job_id, win}
-- win is nil if terminal is not currently visible
function GetTermInfo()
if Lemon.term_buf == nil or not vim.api.nvim_buf_is_valid(Lemon.term_buf) then
return nil
end
local job_id = vim.api.nvim_buf_get_var(Lemon.term_buf, 'terminal_job_id')
-- Find window showing the terminal buffer
local win = nil
for _, w in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_buf(w) == Lemon.term_buf then
win = w
break
end
end
return { buf = Lemon.term_buf, job_id = job_id, win = win }
end
-- Compatibility wrapper - returns window ID if terminal is visible
function SwitchToExistingTerm()
local info = GetTermInfo()
return info and info.win or nil
end
function SaveAllCode()
-- Filetypes you want to save
local valid_ft = {
c = true,
cpp = true,
h = true,
hpp = true,
}
-- Iterate through all buffers
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
-- Only act on listed + loaded buffers
if vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buflisted then
local ft = vim.bo[buf].filetype
-- If filetype matches, write the buffer
if valid_ft[ft] and vim.bo[buf].modified then
-- vim.print("Saving buffer", buf)
vim.api.nvim_buf_call(buf, function()
vim.cmd("write")
end)
end
end
end
end
-- Runs the make command and runs the callback when it completes
function MakeAnd(run_callback, silent_)
local silent
if silent_ ~= nil then
silent = silent_
else
silent = false
end
-- Create a one-time autocmd that fires when make completes
local group = vim.api.nvim_create_augroup('MakeAnd', { clear = false })
local wnd = vim.api.nvim_get_current_win()
local pos = vim.api.nvim_win_get_cursor(wnd)
vim.api.nvim_create_autocmd('QuickFixCmdPost', {
group = group,
pattern = 'make',
once = true,
callback = function()
local qf_list = vim.fn.getqflist()
local has_errors = false
for _, item in ipairs(qf_list) do
if item.valid == 1 then
has_errors = true
break
end
end
vim.schedule(function()
if not has_errors then
vim.api.nvim_echo({ { "Build succeeded", "Normal" } }, false, {})
run_callback()
vim.api.nvim_win_set_cursor(wnd, pos)
else
vim.api.nvim_echo({ { "Build failed", "ErrorMsg" } }, false, {})
end
end)
end
})
if silent then
vim.cmd('silent make!')
else
vim.cmd('make!')
end
end
function TabCurrent()
return vim.fn.tabpagenr()
end
function TabSwitch(tab)
vim.cmd('tabnext ' .. tab)
end

33
lua/snippets.lua Normal file
View File

@@ -0,0 +1,33 @@
-- Header guard snippet
vim.keymap.set('n', ',hg', function()
local function uuid()
local template = 'xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'
return string.gsub(template, '[xy]', function(c)
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
return string.format('%X', v)
end)
end
local v = '_' .. uuid()
vim.api.nvim_put({ '#ifndef ' .. v,
"#define " .. v,
"",
"namespace ng {",
"",
"}",
"",
'#endif // ' .. v,
}, 'c', false, true)
vim.cmd("normal! 3k")
end, { desc = 'Inser header boiler plate' })
vim.keymap.set('n', ',xx', function()
local cmd = vim.api.nvim_replace_termcodes(":s/x/y/g<Left><Left>", true, true, true)
vim.api.nvim_feedkeys(cmd, "n", false)
end)
vim.keymap.set('n', ',xty', function()
vim.api.nvim_feedkeys("yyp", "n", false)
local cmd = vim.api.nvim_replace_termcodes(":s/x/y/g<CR>", true, true, true)
vim.api.nvim_feedkeys(cmd, "n", false)
end)

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
APP=~/.local/share/Steam/steamapps/common/OxygenNotIncluded APP=~/.local/share/Steam/steamapps/common/OxygenNotIncluded
SAVE="~/.config/unity3d/Klei/Oxygen Not Included" SAVE=~/.config/unity3d/Klei/Oxygen\ Not\ Included
set -ex set -ex
ln -sf "$APP/OxygenNotIncluded_Data/Managed" Libs ln -sf "$APP/OxygenNotIncluded_Data/Managed" Libs

View File

@@ -1,271 +0,0 @@
/*
* Copyright 2022 Peter Han
* 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.
*/
using PeterHan.PLib.Core;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace PeterHan.PLib.Buildings {
/// <summary>
/// A visualizer that colors cells with an overlay when a building is selected or being
/// previewed.
/// </summary>
public abstract class ColoredRangeVisualizer : KMonoBehaviour {
/// <summary>
/// The anim name to use when visualizing.
/// </summary>
private const string ANIM_NAME = "transferarmgrid_kanim";
/// <summary>
/// The animations to play when the visualization is created.
/// </summary>
private static readonly HashedString[] PRE_ANIMS = new HashedString[] {
"grid_pre",
"grid_loop"
};
/// <summary>
/// The animation to play when the visualization is destroyed.
/// </summary>
private static readonly HashedString POST_ANIM = "grid_pst";
/// <summary>
/// The layer on which to display the visualizer.
/// </summary>
public Grid.SceneLayer Layer { get; set; }
// These components are automatically populated by KMonoBehaviour
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable CS0649
[MyCmpGet]
protected BuildingPreview preview;
[MyCmpGet]
protected Rotatable rotatable;
#pragma warning restore CS0649
#pragma warning restore IDE0044 // Add readonly modifier
/// <summary>
/// The cells where animations are being displayed.
/// </summary>
private readonly HashSet<VisCellData> cells;
protected ColoredRangeVisualizer() {
cells = new HashSet<VisCellData>();
Layer = Grid.SceneLayer.FXFront;
}
/// <summary>
/// Creates or updates the visualizers as necessary.
/// </summary>
private void CreateVisualizers() {
var visCells = HashSetPool<VisCellData, ColoredRangeVisualizer>.Allocate();
var newCells = ListPool<VisCellData, ColoredRangeVisualizer>.Allocate();
try {
if (gameObject != null)
VisualizeCells(visCells);
// Destroy cells that are not used in the new one
foreach (var cell in cells)
if (visCells.Remove(cell))
newCells.Add(cell);
else
cell.Destroy();
// Newcomers get their controller created and added to the list
foreach (var newCell in visCells) {
newCell.CreateController(Layer);
newCells.Add(newCell);
}
// Copy back to global
cells.Clear();
foreach (var cell in newCells)
cells.Add(cell);
} finally {
visCells.Recycle();
newCells.Recycle();
}
}
/// <summary>
/// Called when cells are changed in the building radius.
/// </summary>
private void OnCellChange() {
CreateVisualizers();
}
protected override void OnCleanUp() {
Unsubscribe((int)GameHashes.SelectObject);
if (preview != null) {
Singleton<CellChangeMonitor>.Instance.UnregisterCellChangedHandler(transform,
OnCellChange);
if (rotatable != null)
Unsubscribe((int)GameHashes.Rotated);
}
RemoveVisualizers();
base.OnCleanUp();
}
/// <summary>
/// Called when the object is rotated.
/// </summary>
private void OnRotated(object _) {
CreateVisualizers();
}
/// <summary>
/// Called when the object is selected.
/// </summary>
/// <param name="data">true if selected, or false if deselected.</param>
private void OnSelect(object data) {
if (data is bool selected) {
var position = transform.position;
// Play the appropriate sound and update the visualizers
if (selected) {
PGameUtils.PlaySound("RadialGrid_form", position);
CreateVisualizers();
} else {
PGameUtils.PlaySound("RadialGrid_disappear", position);
RemoveVisualizers();
}
}
}
protected override void OnSpawn() {
base.OnSpawn();
Subscribe((int)GameHashes.SelectObject, OnSelect);
if (preview != null) {
// Previews can be moved
Singleton<CellChangeMonitor>.Instance.RegisterCellChangedHandler(transform,
OnCellChange, nameof(ColoredRangeVisualizer) + ".OnSpawn");
if (rotatable != null)
Subscribe((int)GameHashes.Rotated, OnRotated);
}
}
/// <summary>
/// Removes all of the visualizers.
/// </summary>
private void RemoveVisualizers() {
foreach (var cell in cells)
cell.Destroy();
cells.Clear();
}
/// <summary>
/// Calculates the offset cell from the specified starting point, including the
/// rotation of this object.
/// </summary>
/// <param name="baseCell">The starting cell.</param>
/// <param name="offset">The offset if the building had its default rotation.</param>
/// <returns>The computed destination cell.</returns>
protected int RotateOffsetCell(int baseCell, CellOffset offset) {
if (rotatable != null)
offset = rotatable.GetRotatedCellOffset(offset);
return Grid.OffsetCell(baseCell, offset);
}
/// <summary>
/// Called when cell visualizations need to be updated. Visualized cells should be
/// added to the collection supplied as an argument.
/// </summary>
/// <param name="newCells">The cells which should be visualized.</param>
protected abstract void VisualizeCells(ICollection<VisCellData> newCells);
/// <summary>
/// Stores the data about a particular cell, including its anim controller and tint
/// color.
/// </summary>
protected sealed class VisCellData : IComparable<VisCellData> {
/// <summary>
/// The target cell.
/// </summary>
public int Cell { get; }
/// <summary>
/// The anim controller for this cell.
/// </summary>
public KBatchedAnimController Controller { get; private set; }
/// <summary>
/// The tint used for this cell.
/// </summary>
public Color Tint { get; }
/// <summary>
/// Creates a visualized cell.
/// </summary>
/// <param name="cell">The cell to visualize.</param>
public VisCellData(int cell) : this(cell, Color.white) { }
/// <summary>
/// Creates a visualized cell.
/// </summary>
/// <param name="cell">The cell to visualize.</param>
/// <param name="tint">The color to tint it.</param>
public VisCellData(int cell, Color tint) {
Cell = cell;
Controller = null;
Tint = tint;
}
public int CompareTo(VisCellData other) {
if (other == null)
throw new ArgumentNullException(nameof(other));
return Cell.CompareTo(other.Cell);
}
/// <summary>
/// Creates the anim controller for this cell.
/// </summary>
/// <param name="sceneLayer">The layer on which to display the animation.</param>
public void CreateController(Grid.SceneLayer sceneLayer) {
Controller = FXHelpers.CreateEffect(ANIM_NAME, Grid.CellToPosCCC(Cell,
sceneLayer), null, false, sceneLayer, true);
Controller.destroyOnAnimComplete = false;
Controller.visibilityType = KAnimControllerBase.VisibilityType.Always;
Controller.gameObject.SetActive(true);
Controller.Play(PRE_ANIMS, KAnim.PlayMode.Loop);
Controller.TintColour = Tint;
}
/// <summary>
/// Destroys the anim controller for this cell.
/// </summary>
public void Destroy() {
if (Controller != null) {
Controller.destroyOnAnimComplete = true;
Controller.Play(POST_ANIM, KAnim.PlayMode.Once, 1f, 0f);
Controller = null;
}
}
public override bool Equals(object obj) {
return obj is VisCellData other && other.Cell == Cell && Tint.Equals(other.
Tint);
}
public override int GetHashCode() {
return Cell;
}
public override string ToString() {
return "CellData[cell={0:D},color={1}]".F(Cell, Tint);
}
}
}
}

View File

@@ -5,10 +5,6 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "oni-prio", "mod\oni-prio.csproj", "{B5C0FB18-102D-434C-B8D7-2DDAE54367D6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "oni-prio", "mod\oni-prio.csproj", "{B5C0FB18-102D-434C-B8D7-2DDAE54367D6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "reference\Assembly-CSharp\Assembly-CSharp.csproj", "{501D90AF-582D-4F90-9FE6-1479FBA92A4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-firstpass", "reference\Assembly-CSharp-firstpass\Assembly-CSharp-firstpass.csproj", "{F0AD7234-F5CE-4A5E-8E8A-F621F0B7C0A0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU