Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
69e79d5
feat: Add GitHub Actions, local references, and changelog
flaouve Jul 10, 2026
c3a17a6
fix: Add write permissions for release creation
flaouve Jul 10, 2026
ba1f771
fix: Correct DLL output path to net4.8
flaouve Jul 10, 2026
c65620e
chore: Rename compiled assembly to FlaProjectMER.dll
flaouve Jul 10, 2026
25c298f
feat: Add GitHub Actions, local references, custom logic handler and …
flaouve Jul 10, 2026
9e77aae
fix: Resolve build errors in CustomMapLogicHandler
flaouve Jul 10, 2026
51e2693
revert: Revert custom map logic handler changes to return to v0.1.0 s…
flaouve Jul 10, 2026
8beb429
feat: add interactive animation trigger support on player interaction
flaouve Jul 10, 2026
ffc57af
feat: find animator in target object, its children, or its parents dy…
flaouve Jul 10, 2026
93a89ec
fix: prevent server crash on duplicate object IDs by using ContainsKe…
flaouve Jul 10, 2026
befe21b
feat: add version command to see current project version (0.2.0)
flaouve Jul 10, 2026
e8e8a10
fix: subscribe to both OnInteracted and OnSearched to support both in…
flaouve Jul 10, 2026
2045faf
feat: add debug config option and print detailed interaction logs if …
flaouve Jul 10, 2026
5b4a2ea
bump: version 0.2.1
flaouve Jul 10, 2026
37e5736
feat: rename Debug to lowercase debug and bump version to 0.2.2
flaouve Jul 10, 2026
79a78ea
fix: use Logger.Info for debug output so it always shows in console (…
flaouve Jul 10, 2026
51182be
feat: add Priority custom property support for Waypoint block and bum…
flaouve Jul 10, 2026
648f83b
feat: implement attachments code and number of uses mapping for Picku…
flaouve Jul 10, 2026
9bca1a0
docs: write changelog for versions 0.2.0 through 0.2.5
flaouve Jul 10, 2026
5114383
fix: change AssemblyName back to ProjectMER for compatibility with ME…
flaouve Jul 11, 2026
ec9e5a6
fix: update release workflow file path to match ProjectMER.dll
flaouve Jul 11, 2026
8d920ce
Testt webhook
flaouve Jul 18, 2026
5189813
Update README.md
flaouve Jul 18, 2026
1eb661f
fix: change NetworkIsStatic application order to post-spawn, add stat…
flaouve Jul 18, 2026
8ea6ca1
fix: NetworkIsStatic default overrides and trigger client parenting sync
flaouve Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Build and Release

on:
push:
tags:
- 'v*'

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x' # Fits LangVersion 12 and target framework net48 builds on linux

- name: Restore dependencies
run: dotnet restore ProjectMER.csproj

- name: Build ProjectMER
run: dotnet build ProjectMER.csproj -c Release

- name: Get Changelog Entry
id: changelog
run: |
# Get the tag name (e.g., v1.0.0)
TAG_NAME=${{ github.ref_name }}
# Remove the 'v' prefix to match version in CHANGELOG (e.g., 1.0.0)
VERSION=${TAG_NAME#v}

echo "Extracting changelog for version $VERSION..."

if [ -f CHANGELOG.md ]; then
# Extract lines between the matching version header and the next header
# Matches markdown headers like "## [1.0.0]" or "## 1.0.0"
CHANGELOG_CONTENT=$(sed -n "/^## \[\{0,1\}$VERSION\]\{0,1\}/,/^## /p" CHANGELOG.md | sed '1d;$d')
else
CHANGELOG_CONTENT="Automated release for $TAG_NAME"
fi

# Escape multi-line string for github actions output
EOF=$(dd if=/dev/urandom bs=15 count=1 2>/dev/null | base64 | tr -dc 'a-zA-Z0-9')
echo "notes<<$EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body: ${{ steps.changelog.outputs.notes }}
files: bin/Release/net4.8/ProjectMER.dll
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.2.6]
- **Compatibility:** Restored AssemblyName to `ProjectMER` (from `FlaProjectMER`) to resolve loading issues with dependent plugins such as `MEROptimizer`.

## [0.2.5]
- **Pickup:** Added support for weapon attachments code serialization and deserialization.
- **Pickup:** Added support for Number of Uses (Uses property) to allow finite or infinite item spawns.
- **Pickup:** Added Locked property support to turn standard pickups into interactable buttons.

## [0.2.4]
- **Waypoint:** Added Priority property support (0-255) for bot navigation node configurations.

## [0.2.3]
- **Debug:** Replaced `Logger.Debug` calls inside debug blocks with `Logger.Info` to bypass LabAPI's framework debug log suppression.

## [0.2.2]
- **Interactable:** Fixed Animator lookup fallback chain (self -> children -> parents).
- **Core:** Added version metadata configuration check.

## [0.2.1]
- **Version:** Added `/version` utility commands (`mp version`, `mp ver`, `mp v`) to query active build version in RA console.

## [0.2.0]
- **Interactable:** Changed `TargetAnimator` property to `TargetObject` of type GameObject for simpler drag-and-drop operations in Unity Editor.
- **Core:** Prevented server crash due to duplicate Object IDs in schematic block deserialization.

## [1.0.0]
- Forked ProjectMER to FlaProjectMER.
- Added local dependency reference configurations.
- Integrated GitHub Actions CI/CD workflow for automated building and releases.
3 changes: 3 additions & 0 deletions Commands/MapEditorParentCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using ProjectMER.Commands.ToolGunLike;
using ProjectMER.Commands.Utility;


namespace ProjectMER.Commands;

/// <summary>
Expand All @@ -35,6 +36,8 @@ public override void LoadGeneratedCommands()
RegisterCommand(new List());
RegisterCommand(new Indicators());
RegisterCommand(new Merge());
RegisterCommand(new VersionCommand());
RegisterCommand(new StaticCheck());

RegisterCommand(new Position());
RegisterCommand(new Rotation());
Expand Down
114 changes: 114 additions & 0 deletions Commands/Utility/StaticCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using AdminToys;
using System.Text;
using CommandSystem;
using LabApi.Features.Permissions;
using NorthwoodLib.Pools;
using ProjectMER.Features;
using ProjectMER.Features.Objects;
using UnityEngine;

namespace ProjectMER.Commands.Utility;

/// <summary>
/// TEST COMMAND: Reports how many AdminToy blocks in a loaded schematic
/// have NetworkIsStatic=true vs false. Used to verify the Static flag fix.
///
/// Usage: mp staticcheck [schematicName]
/// </summary>
public class StaticCheck : ICommand
{
/// <inheritdoc/>
public string Command => "staticcheck";

/// <inheritdoc/>
public string[] Aliases { get; } = ["sc", "statcheck"];

/// <inheritdoc/>
public string Description => "[TEST] Checks how many blocks in a loaded schematic have NetworkIsStatic=true. Used to verify the Static flag fix.";

/// <inheritdoc/>
public bool SanitizeResponse => false;

/// <inheritdoc/>
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (!sender.HasAnyPermission($"mpr.{Command}"))
{
response = $"You don't have permission to execute this command. Required permission: mpr.{Command}";
return false;
}

StringBuilder sb = StringBuilderPool.Shared.Rent();
sb.AppendLine();

// Find all active SchematicObjects in the scene
SchematicObject[] allSchematics = UnityEngine.Object.FindObjectsOfType<SchematicObject>();

if (allSchematics.Length == 0)
{
response = "<color=yellow>No schematics are currently loaded/spawned.</color>";
return false;
}

IEnumerable<SchematicObject> targets = arguments.Count >= 1
? allSchematics.Where(s => s.Name.Equals(arguments.At(0), StringComparison.OrdinalIgnoreCase))
: allSchematics;

if (!targets.Any())
{
response = $"No loaded schematic found with name: {arguments.At(0)}\nLoaded: {string.Join(", ", allSchematics.Select(s => s.Name))}";
return false;
}

int totalBlocks = 0;
int staticTrue = 0;
int staticFalse = 0;

foreach (SchematicObject schematic in targets)
{
sb.AppendLine($"<color=orange><b>Schematic: {schematic.Name}</b></color>");

int schStatic = 0;
int schNonStatic = 0;

foreach (GameObject block in schematic.AttachedBlocks)
{
if (!block.TryGetComponent(out AdminToyBase toy))
continue;

if (toy.IsStatic)
schStatic++;
else
schNonStatic++;
}

int schTotal = schStatic + schNonStatic;
totalBlocks += schTotal;
staticTrue += schStatic;
staticFalse += schNonStatic;

sb.AppendLine($" AdminToy blocks : <b>{schTotal}</b>");
sb.AppendLine($" <color=green>NetworkIsStatic = true : {schStatic}</color>");
sb.AppendLine($" <color=red>NetworkIsStatic = false : {schNonStatic}</color>");

if (schTotal > 0)
{
float pct = (float)schStatic / schTotal * 100f;
string verdict = pct >= 99f
? "<color=green><b>✅ FIX WORKING</b></color>"
: pct == 0f
? "<color=red><b>❌ FIX NOT WORKING — all blocks still non-static!</b></color>"
: $"<color=yellow><b>⚠️ PARTIAL ({pct:F0}% static)</b></color>";
sb.AppendLine($" {verdict}");
}

sb.AppendLine();
}

sb.AppendLine($"<b>TOTAL: {staticTrue}/{totalBlocks} blocks have NetworkIsStatic=true</b>");

response = StringBuilderPool.Shared.ToStringReturn(sb);
return true;
}

}
26 changes: 26 additions & 0 deletions Commands/Utility/VersionCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using CommandSystem;
using LabApi.Features.Permissions;

namespace ProjectMER.Commands.Utility;

public class VersionCommand : ICommand
{
public string Command => "version";

public string[] Aliases => ["ver", "v"];

public string Description => "Shows the current version of ProjectMER.";

public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (!sender.HasAnyPermission($"mpr.{Command}"))
{
response = $"You don't have permission to execute this command. Required permission: mpr.{Command}";
return false;
}

response = $"ProjectMER version: <color=yellow>{ProjectMER.Singleton.Version}</color>";
return true;
}
}
3 changes: 3 additions & 0 deletions Configs/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ public class Config
[Description("Enables FileSystemWatcher in this plugin. What it does is when you manually change values in a currently loaded map file, after saving the file the plugin will automatically reload the map in-game with the new changes so you won't need to do it yourself.")]
public bool EnableFileSystemWatcher { get; set; } = false;

[Description("Whether to show debug logs in the console.")]
public bool debug { get; set; } = false;

[Description("Whether the object will be auto selected when spawning it.")]
public bool AutoSelect { get; set; } = true;

Expand Down
21 changes: 20 additions & 1 deletion Features/Objects/SchematicObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,33 @@ private void CreateRecursiveFromID(int id, List<SchematicBlockData> blocks, Tran
GameObject gameObject = block.Create(this, parentTransform);
NetworkServer.Spawn(gameObject);

ObjectFromId.Add(block.ObjectId, gameObject.transform);
// Apply NetworkIsStatic / NetworkMovementSmoothing AFTER spawning.
// Setting Mirror SyncVars before NetworkServer.Spawn() causes initialization
// order issues where the static flag is silently lost, resulting in the server
// sending position updates every tick even for fully static objects.
if (gameObject.TryGetComponent(out AdminToyBase adminToyBase))
{
adminToyBase.NetworkIsStatic = block.IsStatic;
if (!block.IsStatic)
adminToyBase.NetworkMovementSmoothing = 60;

// Reset parent to trigger Unity's OnTransformParentChanged callback.
// This forces RpcChangeParent to run after spawning, syncing the parent-child relationship to clients.
gameObject.transform.SetParent(null);
gameObject.transform.SetParent(parentTransform);
gameObject.transform.SetLocalPositionAndRotation(block.Position, Quaternion.Euler(block.Rotation));
}

if (!ObjectFromId.ContainsKey(block.ObjectId))
ObjectFromId.Add(block.ObjectId, gameObject.transform);

if (block.BlockType != BlockType.Light && TryGetAnimatorController(block.AnimatorName, out RuntimeAnimatorController animatorController))
_animators.Add(gameObject, animatorController);

return gameObject.transform;
}


private bool TryGetAnimatorController(string animatorName, out RuntimeAnimatorController animatorController)
{
animatorController = null!;
Expand Down
Loading