Jump to content

Recommended Posts

Posted (edited)

English:

Could you please tell me how to use the mod to generate a "Hefty Red meat-Carrot stew" at the coordinate position "512081, 112, 512051" with the "/zuofan" command? I have successfully generated items at the coordinate position "512081, 112, 512051" using the "/zuofan" command through "wiki.vintagestory.at and ChatGPT", but I can only generate blocks or empty "clay pots", and I can't generate a "Dark brown ceramic pot of meat stew" like a "clay pot with Hefty Red meat-Carrot stew". Here is my code.

—————————————————————————————————————————————————————————————————————————————————————————————————

中文:

请问,如何通过模组实现用“/zuofan”指令在“512081, 112, 512051”这个坐标位置生成一份"豪华红肉-胡萝卜炖"。
我通过“wiki.vintagestory.at和ChatGPT”成功实现了用“/zuofan”指令在“512081, 112, 512051”这个坐标位置生成物品,但是只能生成方块或者空的"陶锅",无法生成"装着炖肉的深棕陶锅"这种有食物的陶锅比如装着"豪华红肉-胡萝卜炖"的陶锅。下面是我的代码。

—————————————————————————————————————————————————————————————————————————————————————————————————

using System;
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;
using Vintagestory.API.Server;
using Vintagestory.GameContent;

public class ZengruiModSystem : ModSystem
{
    ICoreServerAPI sapi;

    public override void StartServerSide(ICoreServerAPI api)
    {
        sapi = api;

        // 指令 1:获取玩家脚下坐标
        api.ChatCommands
            .Create("huodezuobiao")
            .WithDescription("显示玩家脚下方块的绝对坐标")
            .RequiresPrivilege(Privilege.chat)
            .HandleWith(OnGetPosition);

        // 指令 2:在固定坐标放置陶锅,并填充食物
        api.ChatCommands
            .Create("zuofan")
            .WithDescription("在固定坐标放置红肉炖胡萝卜或陶锅")
            .RequiresPrivilege(Privilege.chat)
            .HandleWith(OnPlaceFixedBlock);
    }

    // -----------------------------
    // /huodezuobiao 的回调
    private TextCommandResult OnGetPosition(TextCommandCallingArgs args)
    {
        var player = args.Caller.Player;
        if (player == null) return TextCommandResult.Error("未找到玩家。");

        // 直接获取玩家所在块坐标
        BlockPos pos = player.Entity.Pos.AsBlockPos;

        return TextCommandResult.Success($"你脚下的绝对坐标为: X={pos.X}, Y={pos.Y}, Z={pos.Z}");
    }

    // -----------------------------
    // /zengruizuorouduncai 的回调
    private TextCommandResult OnPlaceFixedBlock(TextCommandCallingArgs args)
    {
        // 固定坐标
        BlockPos pos = new BlockPos(512081, 112, 512051);

        // 强制加载区块
        sapi.World.BlockAccessor.GetChunkAtBlockPos(pos);

        // 使用陶锅的 ID (1093) 来获取陶锅方块
        Block crockBlock = sapi.World.GetBlock(1093);  // 请确保陶锅的ID正确
        if (crockBlock == null)
        {
            return TextCommandResult.Error("未找到陶锅方块,请检查 block id");
        }

        // 放置陶锅(只放在目标方块上)
        sapi.World.BlockAccessor.SetBlock(crockBlock.BlockId, pos);

        // 获取该位置的 BlockEntity
        var be = sapi.World.BlockAccessor.GetBlockEntity(pos) as BlockEntityCrock;
        if (be != null)
        {
            // 访问陶锅的 Inventory
            var inv = be.Inventory;
            if (inv != null)
            {
                // 获取 "生红肉" 和 "胡萝卜" 的物品实例
                Item meatItem = sapi.World.GetItem(new AssetLocation("game", "redmeat-raw"));
                Item carrotItem = sapi.World.GetItem(new AssetLocation("game", "vegetable-carrot"));

                if (meatItem == null || carrotItem == null)
                {
                    return TextCommandResult.Error("未能找到生红肉或胡萝卜的物品,请检查物品代码");
                }

                // 创建物品堆栈,假设数量为 1(你可以根据需要调整)
                ItemStack meatStack = new ItemStack(meatItem, 1);
                ItemStack carrotStack = new ItemStack(carrotItem, 1);

                // 直接设置陶锅的槽位内容
                inv[0].Itemstack = meatStack;  // 放入红肉
                inv[1].Itemstack = carrotStack;  // 放入胡萝卜

                // 确保 BlockEntity 更新
                be.MarkDirty(true);
            }
        }

        return TextCommandResult.Success($"已在 {pos.X} {pos.Y} {pos.Z} 放置陶锅,并添加了红肉和胡萝卜");
    }
}
 

—————————————————————————————————————————————————————————————————————————————————————————————————

English:

ChatGPT said that this piece of code of mine can generate a pottery pot filled with food, but the actual result in the world is that it generates an empty pottery pot.

—————————————————————————————————————————————————————————————————————————————————————————————————

中文:

ChatGPT说我这段代码可以生成装有食物的陶锅,但是实际效果是生成一个空的陶锅。

Edited by 快乐守护者
  • Solution
Posted

I made a few changes and got it working:

In `StartServerSide`:

Spoiler
    api.ChatCommands
        .Create("zuofan")
        .WithDescription("Places a stew pot at player pos +3z")
        .RequiresPrivilege(Privilege.chat)
        .HandleWith(args => {
            // Places the pot at player pos +3z instead of the fixed position
            var pos = args.Caller.Pos.AddCopy(0, 0, 3);
            return OnPlaceFixedBlock(args, pos.AsBlockPos);
        });

The handler and a method for Ground Storage:

Spoiler
   private TextCommandResult OnPlaceFixedBlock(TextCommandCallingArgs args, BlockPos pos) {
        var crockCode = "claypot-blue-cooked";
        // Get the crock block using AssetLocation
        var crockBlock = sapi.World.GetBlock(new AssetLocation(crockCode));
        //var crockBlock = sapi.World.GetBlock(1093);

        var crockStack = new ItemStack(crockBlock, 1);

        // To set meal into a container, you need to call `IBlockMealContainer.SetContents`
        IBlockMealContainer mealContainer = crockBlock as IBlockMealContainer;

        if (mealContainer == null) {
            return TextCommandResult.Error("Specified block code is not a meal container");
        }

        // Get item instances for raw red meat and carrots
        Item meatItem = sapi.World.GetItem(
            new AssetLocation("game", "redmeat-raw")
        );
        Item carrotItem = sapi.World.GetItem(
            new AssetLocation("game", "vegetable-carrot")
        );

        if (meatItem == null || carrotItem == null) {
            return TextCommandResult.Error(
                "Failed to find raw red meat or carrot items. Please check the item codes."
            );
        }

        // Create item stacks (quantity can be adjusted as needed)
        ItemStack meatStack = new ItemStack(meatItem, 1);
        ItemStack carrotStack = new ItemStack(carrotItem, 1);

        // Call `IBlockMealContainer.SetContents`
        mealContainer.SetContents(
            "meatystew",
            crockStack,
            [
                 meatStack.Clone(),
                 meatStack.Clone(),
                 carrotStack.Clone(),
                 carrotStack.Clone()
            ]
        );

        // In recent VS versions, meal containers are placed in Ground Storages instead of being placed directly
        // So we follow the way
        PlaceOneItemStackAsGS(crockStack, pos);

        return TextCommandResult.Success(
            $"Placed a crock at {pos.X} {pos.Y} {pos.Z} and added red meat and carrots."
        );
    }

    /// <summary>
    /// Create a Ground Storage, place the gs at `pos` in the world and set `itemStack` into it
    /// </summary>
    /// <param name="itemStack"></param>
    /// <param name="pos"></param>
    public void PlaceOneItemStackAsGS(ItemStack itemStack, BlockPos pos) {
        Block blockgs = sapi.World.GetBlock(new AssetLocation("groundstorage"));
        sapi.World.BlockAccessor.SetBlock(blockgs.Id, pos);
        BlockEntityGroundStorage begs = sapi.World.BlockAccessor.GetBlockEntity(pos) as BlockEntityGroundStorage;

        var dummySlot = new DummySlot(itemStack);
        begs.DetermineStorageProperties(dummySlot);

        CollectibleBehaviorGroundStorable behavior = itemStack.Collectible.GetBehavior<CollectibleBehaviorGroundStorable>();
        var groundStorageProperties = ((behavior != null) ? behavior.StorageProps : null);
        begs.ForceStorageProps(groundStorageProperties);
        begs.Inventory[0].Itemstack = itemStack.Clone();
        begs.Inventory[0].MarkDirty();
    }

Changes and reasons:
- Spawning the pot at fixed position was hard to debug, so changed the command to spawn it at player pos +3z
  - So after running the command, look south to find the spawned pot
- Getting block/item by raw ID doesn't work well. Use it with `AssetLocation` always.
  - Actually `GetBlock(1093)` gave me one of variants of brick blocks :D
- Meat stew requires at least 2 meat, so changed the ingredient to 2 red meat and 2 carrots 
- To set meal into a pot properly, you need to call `IBlockMealContainer.SetContents`
- In recent VS versions, meal containers are placed in Ground Storages instead of being placed directly, so changed the code to follow the way
  - Create a pot stack, set meal content to it, create a Ground Storage, place the gs and set the pot stack into it

  • Like 1
Posted
On 1/12/2026 at 7:59 PM, Spoonail said:

I made a few changes and got it working:

In `StartServerSide`:

  Reveal hidden contents
    api.ChatCommands
        .Create("zuofan")
        .WithDescription("Places a stew pot at player pos +3z")
        .RequiresPrivilege(Privilege.chat)
        .HandleWith(args => {
            // Places the pot at player pos +3z instead of the fixed position
            var pos = args.Caller.Pos.AddCopy(0, 0, 3);
            return OnPlaceFixedBlock(args, pos.AsBlockPos);
        });

The handler and a method for Ground Storage:

  Reveal hidden contents
   private TextCommandResult OnPlaceFixedBlock(TextCommandCallingArgs args, BlockPos pos) {
        var crockCode = "claypot-blue-cooked";
        // Get the crock block using AssetLocation
        var crockBlock = sapi.World.GetBlock(new AssetLocation(crockCode));
        //var crockBlock = sapi.World.GetBlock(1093);

        var crockStack = new ItemStack(crockBlock, 1);

        // To set meal into a container, you need to call `IBlockMealContainer.SetContents`
        IBlockMealContainer mealContainer = crockBlock as IBlockMealContainer;

        if (mealContainer == null) {
            return TextCommandResult.Error("Specified block code is not a meal container");
        }

        // Get item instances for raw red meat and carrots
        Item meatItem = sapi.World.GetItem(
            new AssetLocation("game", "redmeat-raw")
        );
        Item carrotItem = sapi.World.GetItem(
            new AssetLocation("game", "vegetable-carrot")
        );

        if (meatItem == null || carrotItem == null) {
            return TextCommandResult.Error(
                "Failed to find raw red meat or carrot items. Please check the item codes."
            );
        }

        // Create item stacks (quantity can be adjusted as needed)
        ItemStack meatStack = new ItemStack(meatItem, 1);
        ItemStack carrotStack = new ItemStack(carrotItem, 1);

        // Call `IBlockMealContainer.SetContents`
        mealContainer.SetContents(
            "meatystew",
            crockStack,
            [
                 meatStack.Clone(),
                 meatStack.Clone(),
                 carrotStack.Clone(),
                 carrotStack.Clone()
            ]
        );

        // In recent VS versions, meal containers are placed in Ground Storages instead of being placed directly
        // So we follow the way
        PlaceOneItemStackAsGS(crockStack, pos);

        return TextCommandResult.Success(
            $"Placed a crock at {pos.X} {pos.Y} {pos.Z} and added red meat and carrots."
        );
    }

    /// <summary>
    /// Create a Ground Storage, place the gs at `pos` in the world and set `itemStack` into it
    /// </summary>
    /// <param name="itemStack"></param>
    /// <param name="pos"></param>
    public void PlaceOneItemStackAsGS(ItemStack itemStack, BlockPos pos) {
        Block blockgs = sapi.World.GetBlock(new AssetLocation("groundstorage"));
        sapi.World.BlockAccessor.SetBlock(blockgs.Id, pos);
        BlockEntityGroundStorage begs = sapi.World.BlockAccessor.GetBlockEntity(pos) as BlockEntityGroundStorage;

        var dummySlot = new DummySlot(itemStack);
        begs.DetermineStorageProperties(dummySlot);

        CollectibleBehaviorGroundStorable behavior = itemStack.Collectible.GetBehavior<CollectibleBehaviorGroundStorable>();
        var groundStorageProperties = ((behavior != null) ? behavior.StorageProps : null);
        begs.ForceStorageProps(groundStorageProperties);
        begs.Inventory[0].Itemstack = itemStack.Clone();
        begs.Inventory[0].MarkDirty();
    }

Changes and reasons:
- Spawning the pot at fixed position was hard to debug, so changed the command to spawn it at player pos +3z
  - So after running the command, look south to find the spawned pot
- Getting block/item by raw ID doesn't work well. Use it with `AssetLocation` always.
  - Actually `GetBlock(1093)` gave me one of variants of brick blocks :D
- Meat stew requires at least 2 meat, so changed the ingredient to 2 red meat and 2 carrots 
- To set meal into a pot properly, you need to call `IBlockMealContainer.SetContents`
- In recent VS versions, meal containers are placed in Ground Storages instead of being placed directly, so changed the code to follow the way
  - Create a pot stack, set meal content to it, create a Ground Storage, place the gs and set the pot stack into it

English:
Thank you very much! These two methods have perfectly solved my problem. Now I can generate a pot of food at the specified coordinates "512081, 112, 512051" by using the "/zuofan" command. Here is my code.

 

原文:

非常感谢!这两个方法完美的解决了我的问题。我现在能通过“/zuofan”指令在指定的坐标“512081, 112, 512051”上生成一锅食物了。下面是我的代码。

————————————————————————————————————————————————————————————————
 

using System;
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;
using Vintagestory.API.Server;
using Vintagestory.GameContent;

public class ZengruiModSystem : ModSystem
{
    private ICoreServerAPI sapi;

    public override void StartServerSide(ICoreServerAPI api)
    {
        sapi = api;

        // 指令:获取玩家脚下坐标
        api.ChatCommands
            .Create("huodezuobiao")
            .WithDescription("显示玩家脚下方块的绝对坐标")
            .RequiresPrivilege(Privilege.chat)
            .HandleWith(OnGetPosition);

        // 指令:在固定坐标放置一锅红肉炖胡萝卜
        api.ChatCommands
            .Create("zuofan")
            .WithDescription("在固定坐标放置一锅红肉炖胡萝卜(陶锅)")
            .RequiresPrivilege(Privilege.chat)
            .HandleWith(OnPlaceCookedCrock);
    }

    // --------------------------------------------------
    // /huodezuobiao
    private TextCommandResult OnGetPosition(TextCommandCallingArgs args)
    {
        var player = args.Caller.Player;
        if (player == null)
        {
            return TextCommandResult.Error("未找到玩家");
        }

        BlockPos pos = player.Entity.Pos.AsBlockPos;
        return TextCommandResult.Success(
            $"你脚下的绝对坐标为: X={pos.X}, Y={pos.Y}, Z={pos.Z}"
        );
    }

    // --------------------------------------------------
    // /zuofan
    private TextCommandResult OnPlaceCookedCrock(TextCommandCallingArgs args)
    {
        // ★ 固定坐标(你指定的)
        BlockPos pos = new BlockPos(512081, 112, 512051);

        // 确保区块已加载
        sapi.World.BlockAccessor.GetChunkAtBlockPos(pos);

        // --------------------------------------------------
        // 1. 获取“陶锅物品对应的方块”
        // 注意:这是陶锅“物品”,不是直接放在世界里的方块
        Block crockBlock = sapi.World.GetBlock(new AssetLocation("game", "claypot-blue-cooked"));
        if (crockBlock == null)
        {
            return TextCommandResult.Error("未找到陶锅方块(claypot-blue-cooked)");
        }

        // 创建一个陶锅 ItemStack
        ItemStack crockStack = new ItemStack(crockBlock, 1);

        // --------------------------------------------------
        // 2. 通过 IBlockMealContainer 给陶锅“生成一锅食物”
        IBlockMealContainer mealContainer = crockBlock as IBlockMealContainer;
        if (mealContainer == null)
        {
            return TextCommandResult.Error("该方块不是食物容器(IBlockMealContainer)");
        }

        // 获取食材物品
        Item meatItem = sapi.World.GetItem(new AssetLocation("game", "redmeat-raw"));
        Item carrotItem = sapi.World.GetItem(new AssetLocation("game", "vegetable-carrot"));

        if (meatItem == null || carrotItem == null)
        {
            return TextCommandResult.Error("未能找到红肉或胡萝卜物品");
        }

        // 创建食材 ItemStack
        ItemStack meatStack = new ItemStack(meatItem, 1);
        ItemStack carrotStack = new ItemStack(carrotItem, 1);

        // ★ 核心:生成一锅“红肉炖胡萝卜”
        // "meatystew" 是食谱代码
        mealContainer.SetContents(
            "meatystew",
            crockStack,
            new ItemStack[]
            {
                meatStack.Clone(),
                meatStack.Clone(),
                carrotStack.Clone(),
                carrotStack.Clone()
            }
        );

        // --------------------------------------------------
        // 3. 把“装好食物的陶锅”作为物品,放进地面存放(Ground Storage)
        PlaceOneItemStackAsGroundStorage(crockStack, pos);

        return TextCommandResult.Success(
            $"已在 {pos.X}, {pos.Y}, {pos.Z} 放置一锅红肉炖胡萝卜"
        );
    }

    // --------------------------------------------------
    // 在指定坐标创建 GroundStorage,并放入一个物品
    private void PlaceOneItemStackAsGroundStorage(ItemStack itemStack, BlockPos pos)
    {
        // 获取地面存放方块
        Block groundStorageBlock = sapi.World.GetBlock(new AssetLocation("game", "groundstorage"));
        sapi.World.BlockAccessor.SetBlock(groundStorageBlock.BlockId, pos);

        // 获取地面存放的 BlockEntity
        BlockEntityGroundStorage begs =
            sapi.World.BlockAccessor.GetBlockEntity(pos) as BlockEntityGroundStorage;

        if (begs == null) return;

        // 使用 DummySlot 让系统判断存放属性
        ItemSlot dummySlot = new DummySlot(itemStack);
        begs.DetermineStorageProperties(dummySlot);

        // 强制应用 GroundStorage 的存放属性
        CollectibleBehaviorGroundStorable behavior =
            itemStack.Collectible.GetBehavior<CollectibleBehaviorGroundStorable>();

        if (behavior != null)
        {
            begs.ForceStorageProps(behavior.StorageProps);
        }

        // 放入物品
        begs.Inventory[0].Itemstack = itemStack.Clone();
        begs.Inventory[0].MarkDirty();

        // 通知世界刷新
        begs.MarkDirty(true);
    }
}
 

  • Like 1
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.