Compare commits

...

11 Commits

21 changed files with 229 additions and 101 deletions

View File

@@ -1,5 +1,5 @@
{ {
"total": 18477, "total": 22680,
"sessions": [ "sessions": [
{ {
"begin": "2026-03-17T23:54:43+01:00", "begin": "2026-03-17T23:54:43+01:00",
@@ -25,6 +25,36 @@
"begin": "2026-03-18T23:14:52+01:00", "begin": "2026-03-18T23:14:52+01:00",
"end": "2026-03-19T01:17:26+01:00", "end": "2026-03-19T01:17:26+01:00",
"duration": 7354 "duration": 7354
},
{
"begin": "2026-05-31T01:21:35+02:00",
"end": "2026-05-31T01:44:30+02:00",
"duration": 1374
},
{
"begin": "2026-05-31T01:44:30+02:00",
"end": "2026-05-31T01:51:55+02:00",
"duration": 445
},
{
"begin": "2026-05-31T01:52:40+02:00",
"end": "2026-05-31T02:00:30+02:00",
"duration": 470
},
{
"begin": "2026-05-31T02:24:20+02:00",
"end": "2026-05-31T02:49:01+02:00",
"duration": 1481
},
{
"begin": "2026-05-31T03:09:33+02:00",
"end": "2026-05-31T03:10:03+02:00",
"duration": 29
},
{
"begin": "2026-05-31T03:10:07+02:00",
"end": "2026-05-31T03:16:52+02:00",
"duration": 404
} }
] ]
} }

4
.vscode/tasks.json vendored
View File

@@ -4,9 +4,9 @@
{ {
"label": "check env", "label": "check env",
"type": "shell", "type": "shell",
"command": "[ -n \"$VINTAGE_STORY\" ] || { echo 'ERROR: VINTAGE_STORY is not set'; exit 1; }; [ -n \"$VINTAGE_STORY_DATA\" ] || { echo 'ERROR: VINTAGE_STORY_DATA is not set'; exit 1; }", "command": "if [ \"${input:buildConfig}\" = \"Version22\" ]; then [ -n \"$VINTAGE_STORY_22\" ] || { echo 'ERROR: VINTAGE_STORY_22 is not set'; exit 1; }; [ -n \"$VINTAGE_STORY_DATA_22\" ] || { echo 'ERROR: VINTAGE_STORY_DATA_22 is not set'; exit 1; }; else [ -n \"$VINTAGE_STORY_21\" ] || { echo 'ERROR: VINTAGE_STORY_21 is not set'; exit 1; }; [ -n \"$VINTAGE_STORY_DATA_21\" ] || { echo 'ERROR: VINTAGE_STORY_DATA_21 is not set'; exit 1; }; fi",
"windows": { "windows": {
"command": "if (-not $env:VINTAGE_STORY) { Write-Host 'ERROR: VINTAGE_STORY is not set'; exit 1 }; if (-not $env:VINTAGE_STORY_DATA) { Write-Host 'ERROR: VINTAGE_STORY_DATA is not set'; exit 1 }" "command": "if ('${input:buildConfig}' -eq 'Version22') { if (-not $env:VINTAGE_STORY_22) { Write-Host 'ERROR: VINTAGE_STORY_22 is not set'; exit 1 }; if (-not $env:VINTAGE_STORY_DATA_22) { Write-Host 'ERROR: VINTAGE_STORY_DATA_22 is not set'; exit 1 } } else { if (-not $env:VINTAGE_STORY_21) { Write-Host 'ERROR: VINTAGE_STORY_21 is not set'; exit 1 }; if (-not $env:VINTAGE_STORY_DATA_21) { Write-Host 'ERROR: VINTAGE_STORY_DATA_21 is not set'; exit 1 } }"
}, },
"problemMatcher": [] "problemMatcher": []
}, },

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Text;
using Vintagestory.API.Client; using Vintagestory.API.Client;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.Datastructures; using Vintagestory.API.Datastructures;
@@ -110,6 +111,46 @@ public class BlockEntityButterflyPinBoard : BlockEntityDisplay
} }
} }
public override void GetBlockInfo(IPlayer forPlayer, StringBuilder dsc)
{
int highlightedSlot = -1;
BlockSelection? selection = forPlayer?.CurrentBlockSelection;
if (selection?.Position != null && selection.Position.Equals(Pos))
highlightedSlot = GetSlotIndex(selection.HitPosition);
AppendContentsBlockInfo(dsc, highlightedSlot);
}
public bool AppendContentsBlockInfo(StringBuilder builder, int highlightedSlot)
{
int initialLength = builder.Length;
for (int slotIndex = 0; slotIndex < ActiveSlotCount; slotIndex++)
{
ItemStack? stack = inventory[slotIndex].Itemstack;
if (stack == null)
continue;
if (builder.Length > initialLength)
builder.AppendLine();
else if (initialLength > 0)
builder.AppendLine();
string itemName = stack.GetName();
if (slotIndex == highlightedSlot)
{
builder.Append("<font color=\"#00ffff\">");
builder.Append(itemName);
builder.Append("</font>");
continue;
}
builder.Append(itemName);
}
return builder.Length > initialLength;
}
public int GetSlotIndex(Vec3d hitPosition) public int GetSlotIndex(Vec3d hitPosition)
{ {
Vec3d local = ToBaseOrientation(hitPosition); Vec3d local = ToBaseOrientation(hitPosition);
@@ -120,6 +161,9 @@ public class BlockEntityButterflyPinBoard : BlockEntityDisplay
double height = (1 - (BoardMargin * 2)) / Rows; double height = (1 - (BoardMargin * 2)) / Rows;
int column = GameMath.Clamp((int)((local.X - BoardMargin) / width), 0, Columns - 1); int column = GameMath.Clamp((int)((local.X - BoardMargin) / width), 0, Columns - 1);
if (IsNorthSouthVariant())
column = (Columns - 1) - column;
int rowFromBottom = GameMath.Clamp((int)((local.Y - BoardMargin) / height), 0, Rows - 1); int rowFromBottom = GameMath.Clamp((int)((local.Y - BoardMargin) / height), 0, Rows - 1);
int rowFromTop = Rows - 1 - rowFromBottom; int rowFromTop = Rows - 1 - rowFromBottom;
@@ -205,6 +249,12 @@ public class BlockEntityButterflyPinBoard : BlockEntityDisplay
}; };
} }
private bool IsNorthSouthVariant()
{
string side = Block?.Variant?["side"] ?? "south";
return side is "north" or "south";
}
public float GetRenderScale() public float GetRenderScale()
{ {
return RenderScale; return RenderScale;

View File

@@ -1,5 +1,4 @@
using ButterflyPins.BlockEntities; using ButterflyPins.BlockEntities;
using System;
using Vintagestory.API.Common; using Vintagestory.API.Common;
using Vintagestory.API.MathTools; using Vintagestory.API.MathTools;
@@ -7,8 +6,6 @@ namespace ButterflyPins.Blocks;
public class BlockButterflyPinBoard : Block public class BlockButterflyPinBoard : Block
{ {
private const string ButterflyPinPrefix = "clothes-butterflypin-";
public override bool OnBlockInteractStart(IWorldAccessor world, IPlayer byPlayer, BlockSelection blockSel) public override bool OnBlockInteractStart(IWorldAccessor world, IPlayer byPlayer, BlockSelection blockSel)
{ {
if (blockSel == null) if (blockSel == null)
@@ -38,32 +35,4 @@ public class BlockButterflyPinBoard : Block
base.OnBlockBroken(world, pos, byPlayer, dropQuantityMultiplier); base.OnBlockBroken(world, pos, byPlayer, dropQuantityMultiplier);
} }
public override bool ConsumeCraftingIngredients(ItemSlot[] slots, ItemSlot outputSlot, GridRecipe matchingRecipe)
{
bool preservedPin = false;
foreach (ItemSlot slot in slots)
{
if (slot.Empty)
continue;
if (!preservedPin && IsButterflyPin(slot.Itemstack))
{
preservedPin = true;
continue;
}
slot.TakeOut(1);
slot.MarkDirty();
}
return true;
}
private static bool IsButterflyPin(ItemStack stack)
{
AssetLocation? code = stack.Collectible?.Code;
return code != null && code.Path.StartsWith(ButterflyPinPrefix, StringComparison.Ordinal);
}
} }

View File

@@ -6,6 +6,8 @@
<OutputPath>bin\$(Configuration)\Mods\mod</OutputPath> <OutputPath>bin\$(Configuration)\Mods\mod</OutputPath>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Configurations>Debug;Release;Version22</Configurations> <Configurations>Debug;Release;Version22</Configurations>
<VINTAGE_STORY>$(VINTAGE_STORY_21)</VINTAGE_STORY>
<VINTAGE_STORY_DATA>$(VINTAGE_STORY_DATA_21)</VINTAGE_STORY_DATA>
<VS_CONFIGLIB Condition="'$(VS_CONFIGLIB)' == ''">$([System.Environment]::GetEnvironmentVariable('VS_CONFIGLIB'))</VS_CONFIGLIB> <VS_CONFIGLIB Condition="'$(VS_CONFIGLIB)' == ''">$([System.Environment]::GetEnvironmentVariable('VS_CONFIGLIB'))</VS_CONFIGLIB>
<ConfigLibAvailable Condition="'$(VS_CONFIGLIB)' != '' and Exists('$(VS_CONFIGLIB)')">true</ConfigLibAvailable> <ConfigLibAvailable Condition="'$(VS_CONFIGLIB)' != '' and Exists('$(VS_CONFIGLIB)')">true</ConfigLibAvailable>
<DefineConstants Condition="'$(ConfigLibAvailable)' == 'true'">$(DefineConstants);CONFIGLIB</DefineConstants> <DefineConstants Condition="'$(ConfigLibAvailable)' == 'true'">$(DefineConstants);CONFIGLIB</DefineConstants>

View File

@@ -1,4 +0,0 @@
{
"block-pinboard2x2-*": "Butterfly Pin Board (2x2)",
"block-pinboard3x3-*": "Butterfly Pin Board (3x3)"
}

View File

@@ -1,13 +0,0 @@
{
"ingredientPattern": "_S_\tSBS\t_S_",
"ingredients": {
"S": { "type": "item", "code": "game:stick" },
"B": {
"type": "item",
"code": "game:*-butterflypin-*"
}
},
"width": 3,
"height": 3,
"output": { "type": "block", "code": "pinboard2x2-north" }
}

View File

@@ -1,13 +0,0 @@
{
"ingredientPattern": "S_S\t_B_\tS_S",
"ingredients": {
"S": { "type": "item", "code": "game:stick" },
"B": {
"type": "item",
"code": "game:*-butterflypin-*"
}
},
"width": 3,
"height": 3,
"output": { "type": "block", "code": "pinboard3x3-north" }
}

View File

@@ -7,6 +7,7 @@
{ "name": "Lockable" } { "name": "Lockable" }
], ],
"variantgroups": [ "variantgroups": [
{ "code": "wood", "loadFromProperties": "block/wood" },
{ "code": "side", "loadFromProperties": "abstract/horizontalorientation" } { "code": "side", "loadFromProperties": "abstract/horizontalorientation" }
], ],
"attributes": { "attributes": {
@@ -21,14 +22,17 @@
"reinforcable": true "reinforcable": true
}, },
"creativeinventory": { "creativeinventory": {
"general": ["pinboard2x2-south"], "general": ["pinboard2x2-oak-north"],
"decorative": ["pinboard2x2-south"] "decorative": ["pinboard2x2-oak-north"]
},
"textures": {
"wood": { "base": "game:block/wood/debarked/{wood}" }
}, },
"shapebytype": { "shapebytype": {
"*-north": { "base": "butterflypins:block/pinboard-2x2", "rotateY": 180 }, "*-*-north": { "base": "dusiulkaspinboard:block/pinboard-2x2", "rotateY": 180 },
"*-east": { "base": "butterflypins:block/pinboard-2x2", "rotateY": 90 }, "*-*-east": { "base": "dusiulkaspinboard:block/pinboard-2x2", "rotateY": 90 },
"*-south": { "base": "butterflypins:block/pinboard-2x2", "rotateY": 0 }, "*-*-south": { "base": "dusiulkaspinboard:block/pinboard-2x2", "rotateY": 0 },
"*-west": { "base": "butterflypins:block/pinboard-2x2", "rotateY": 270 } "*-*-west": { "base": "dusiulkaspinboard:block/pinboard-2x2", "rotateY": 270 }
}, },
"blockmaterial": "Wood", "blockmaterial": "Wood",
"drawtype": "json", "drawtype": "json",
@@ -47,10 +51,10 @@
"y2": 0.9375, "y2": 0.9375,
"z2": 1.0, "z2": 1.0,
"rotateYByType": { "rotateYByType": {
"*-north": 180, "*-*-north": 180,
"*-east": 90, "*-*-east": 90,
"*-south": 0, "*-*-south": 0,
"*-west": 270 "*-*-west": 270
} }
}, },
"selectionbox": { "selectionbox": {
@@ -61,10 +65,10 @@
"y2": 0.9375, "y2": 0.9375,
"z2": 1.0, "z2": 1.0,
"rotateYByType": { "rotateYByType": {
"*-north": 180, "*-*-north": 180,
"*-east": 90, "*-*-east": 90,
"*-south": 0, "*-*-south": 0,
"*-west": 270 "*-*-west": 270
} }
}, },
"combustibleProps": { "combustibleProps": {
@@ -72,9 +76,9 @@
"burnDuration": 20 "burnDuration": 20
}, },
"sounds": { "sounds": {
"place": "block/planks", "place": "game:block/planks",
"break": "block/planks", "break": "game:block/planks",
"hit": "block/planks" "hit": "game:block/planks"
}, },
"materialDensity": 400, "materialDensity": 400,
"guiTransform": { "guiTransform": {

View File

@@ -7,6 +7,7 @@
{ "name": "Lockable" } { "name": "Lockable" }
], ],
"variantgroups": [ "variantgroups": [
{ "code": "wood", "loadFromProperties": "block/wood" },
{ "code": "side", "loadFromProperties": "abstract/horizontalorientation" } { "code": "side", "loadFromProperties": "abstract/horizontalorientation" }
], ],
"attributes": { "attributes": {
@@ -21,14 +22,17 @@
"reinforcable": true "reinforcable": true
}, },
"creativeinventory": { "creativeinventory": {
"general": ["pinboard3x3-south"], "general": ["pinboard3x3-oak-north"],
"decorative": ["pinboard3x3-south"] "decorative": ["pinboard3x3-oak-north"]
},
"textures": {
"wood": { "base": "game:block/wood/debarked/{wood}" }
}, },
"shapebytype": { "shapebytype": {
"*-north": { "base": "butterflypins:block/pinboard-3x3", "rotateY": 180 }, "*-*-north": { "base": "dusiulkaspinboard:block/pinboard-3x3", "rotateY": 180 },
"*-east": { "base": "butterflypins:block/pinboard-3x3", "rotateY": 90 }, "*-*-east": { "base": "dusiulkaspinboard:block/pinboard-3x3", "rotateY": 90 },
"*-south": { "base": "butterflypins:block/pinboard-3x3", "rotateY": 0 }, "*-*-south": { "base": "dusiulkaspinboard:block/pinboard-3x3", "rotateY": 0 },
"*-west": { "base": "butterflypins:block/pinboard-3x3", "rotateY": 270 } "*-*-west": { "base": "dusiulkaspinboard:block/pinboard-3x3", "rotateY": 270 }
}, },
"blockmaterial": "Wood", "blockmaterial": "Wood",
"drawtype": "json", "drawtype": "json",
@@ -47,10 +51,10 @@
"y2": 0.9375, "y2": 0.9375,
"z2": 1.0, "z2": 1.0,
"rotateYByType": { "rotateYByType": {
"*-north": 180, "*-*-north": 180,
"*-east": 90, "*-*-east": 90,
"*-south": 0, "*-*-south": 0,
"*-west": 270 "*-*-west": 270
} }
}, },
"selectionbox": { "selectionbox": {
@@ -61,10 +65,10 @@
"y2": 0.9375, "y2": 0.9375,
"z2": 1.0, "z2": 1.0,
"rotateYByType": { "rotateYByType": {
"*-north": 180, "*-*-north": 180,
"*-east": 90, "*-*-east": 90,
"*-south": 0, "*-*-south": 0,
"*-west": 270 "*-*-west": 270
} }
}, },
"combustibleProps": { "combustibleProps": {
@@ -72,9 +76,9 @@
"burnDuration": 25 "burnDuration": 25
}, },
"sounds": { "sounds": {
"place": "block/planks", "place": "game:block/planks",
"break": "block/planks", "break": "game:block/planks",
"hit": "block/planks" "hit": "game:block/planks"
}, },
"materialDensity": 400, "materialDensity": 400,
"guiTransform": { "guiTransform": {

View File

@@ -0,0 +1,4 @@
{
"block-pinboard2x2-*-*": "Butterfly Pin Board (2x2)",
"block-pinboard3x3-*-*": "Butterfly Pin Board (3x3)"
}

View File

@@ -0,0 +1,15 @@
{
"ingredientPattern": "_S_\tSBS\t_S_",
"ingredients": {
"S": { "type": "item", "code": "game:plank-*", "name": "wood", "skipVariants": ["aged", "veryaged"] },
"B": {
"type": "item",
"code": "game:clothes-butterflypin-*",
"isTool": true,
"toolDurabilityCost": 0
}
},
"width": 3,
"height": 3,
"output": { "type": "block", "code": "pinboard2x2-{wood}-north" }
}

View File

@@ -0,0 +1,15 @@
{
"ingredientPattern": "S_S\t_B_\tS_S",
"ingredients": {
"S": { "type": "item", "code": "game:plank-*", "name": "wood", "skipVariants": ["aged", "veryaged"] },
"B": {
"type": "item",
"code": "game:clothes-butterflypin-*",
"isTool": true,
"toolDurabilityCost": 0
}
},
"width": 3,
"height": 3,
"output": { "type": "block", "code": "pinboard3x3-{wood}-north" }
}

BIN
ButterflyPins/modicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,15 +1,16 @@
{ {
"$schema": "https://moddbcdn.vintagestory.at/schema/modinfo.latest.json", "$schema": "https://moddbcdn.vintagestory.at/schema/modinfo.latest.json",
"type": "Code", "type": "Code",
"modid": "butterflypins", "modid": "dusiulkaspinboard",
"name": "Butterfly Pins", "name": "Butterfly Pins",
"authors": [ "authors": [
"OrekiWoof" "OrekiWoof"
], ],
"description": "ButterflyPins mod.", "description": "ButterflyPins mod.",
"version": "1.0.0", "version": "1.0.1",
"dependencies": { "dependencies": {
"game": "1.21.0" "game": "1.21.0"
}, },
"side": "Universal" "requiredOnServer": true,
"requiredOnClient": true
} }

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 OrekiWoof
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.

32
README.md Normal file
View File

@@ -0,0 +1,32 @@
# Butterfly Pins
## Requirements for the solution
- .NET 8 SDK for 1.21
- .NET 10 SDK when building `Version22` for 1.22
- Vintage Story install - 1.21 or 1.22, with environment variables describing the paths
- env vars described below
### Required env vars
- `VINTAGE_STORY_21`: path to the 1.21 game install (on Windows default is `%appdata%/VintageStory`)
- `VINTAGE_STORY_DATA_21`: path to the 1.21 game data directory (on Windows default is `%appdata%/VintageStoryData`)
- `VINTAGE_STORY_22`: path to the 1.22 game install (`Version22` configuration)
- `VINTAGE_STORY_DATA_22`: path to the 1.22 game data directory (`Version22` configuration)
- `VS_CONFIGLIB`: path to `configlib.dll`. If not set, will compile with a warning and without configlib support
## Running
If you know what you're doing, `dotnet build` etc obviously will work. This was created with the official mod template.
### VS Code with `C# Dev Kit` extension
- select appropriate solution configuration: `F1` (run command) -> `.NET: Select a configuration`
- for 1.21 select `Debug` or `Release`, for 1.22 select `Version22`
- run action `Start debugging` or `Start without debugging` (might be `f5` and `ctrl+f5` by default)
### Visual Studio
- use configuration picker at the top - `Debug` or `Release` for 1.21, `Version22` for 1.22
- set ButterflyPins project as the startup project
- run

View File

@@ -4,11 +4,16 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<Configurations>Debug;Release;Version22</Configurations> <Configurations>Debug;Release;Version22</Configurations>
<VINTAGE_STORY>$(VINTAGE_STORY_21)</VINTAGE_STORY>
<VS_CONFIGLIB Condition="'$(VS_CONFIGLIB)' == ''">$([System.Environment]::GetEnvironmentVariable('VS_CONFIGLIB'))</VS_CONFIGLIB> <VS_CONFIGLIB Condition="'$(VS_CONFIGLIB)' == ''">$([System.Environment]::GetEnvironmentVariable('VS_CONFIGLIB'))</VS_CONFIGLIB>
<ConfigLibAvailable Condition="'$(VS_CONFIGLIB)' != '' and Exists('$(VS_CONFIGLIB)')">true</ConfigLibAvailable> <ConfigLibAvailable Condition="'$(VS_CONFIGLIB)' != '' and Exists('$(VS_CONFIGLIB)')">true</ConfigLibAvailable>
<DefineConstants Condition="'$(ConfigLibAvailable)' == 'true'">$(DefineConstants);CONFIGLIB</DefineConstants> <DefineConstants Condition="'$(ConfigLibAvailable)' == 'true'">$(DefineConstants);CONFIGLIB</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Version22'">
<VINTAGE_STORY>$(VINTAGE_STORY_22)</VINTAGE_STORY>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Cake.Frosting" Version="5.0.0" /> <PackageReference Include="Cake.Frosting" Version="5.0.0" />
<PackageReference Include="Cake.Json" Version="7.0.1" /> <PackageReference Include="Cake.Json" Version="7.0.1" />

6
printLatestCommits.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
count="${1:-10}"
git -C "$script_dir" log -n "$count" --pretty=format:%s | awk '{ lines[NR] = $0 } END { for (i = NR; i >= 1; i--) print lines[i] }'