강의 환경 : forge-1.7.10-10.13.4.1517-1.7.10

 

배경지식 :

1. 아이템스택(Item Stack)

 

선행과정 :

1. 기본 모드 파일

 

강의 목표 :

1. 무형 조합법(Shapeless Recipe)를 추가한다

2. 화로 조합법(Smelting Recipe)를 추가한다

 

1. 조합법

조합법은 자신이 추가한 요소를 플레이어에게 제공하는 유용한 수단입니다. 모드 제작자는 자신이 만든 블록과 아이템을 어떠한 방식으로든 플레이어가 사용할 수 있도록 경로를 만들어야 하는데, 조합법을 추가하지 않는다면 엔티티(Entity)를 통한 드롭이나 명령어, 혹은 정말 특수한 방법을 이용해서라도 추가된 아이템 얻을 수 있게 해야 합니다. 조합법은 플레이어 입장에서도 직관적이고 제작자 입장에서도 추가가 용이합니다.

코드를 살펴보기 전에 유형 조합법(Shaped Recipe)과 무형 조합법(Shapeless Recipe)의 차이점을 먼저 알아보겠습니다. 유형 조합법은 조합의 결과가 재료들의 위치에 종속적인 조합법입니다. 즉, 재료들의 위치가 달라지면 결과가 달라지거나 혹은 사라지는 조합법이죠. 대부분의 조합법이 여기에 포함됩니다. 무형 조합법은 반대로 재료들의 위치가 결과에 영향을 끼치지 않는 조합법입니다. 염색 조합과 같은 "섞는" 조합법들이 여기에 속합니다.

그림 1 무형 조합법(Shapeless Recipe)의 예

그림 2 유형 조합법(Shaped Recipe)의 예

그림 3 화로 조합법(Smelting Recipe)의 예

 

2. Shapeless 조합법 추가하기

Shapeless 조합법을 추가해보도록 하겠습니다. 먼저, 기본 모드 파일이 다음과 같이 설정되어 있다고 가정합니다.

@Mod(modid = "basictutorial", name = "Basic Tutorial", version = "1.0.0")

public class BasicTutorial {

    @SidedProxy(clientSide = "oortcloud.basictutorial.ClientProxy", serverSide = "oortcloud.basictutorial.CommonProxy")

    public static CommonProxy proxy;

    

    @Mod.EventHandler

    public static void preInit(FMLPreInitializationEvent event) {

    }

 

    @Mod.EventHandler

    public static void Init(FMLInitializationEvent event) {

    }

 

    @Mod.EventHandler

    public static void postInit(FMLPostInitializationEvent event) {

    }

}

조합법은 일반적으로 Pre-Initialization 상태에서 추가합니다. 만약 다른 모드로부터 조합법이 영향을 받는다면 Initialization이나 Post-Initialization에서 추가할 수도 있으나, 가능하면 Pre-Initialization에서 추가하는 것이 바람직합니다. 다른 모드에서 여러분이 추가할 조합법을 참조할 수도 있기 때문입니다.

Shapeless 조합법은 다음의 함수를 통해서 추가합니다.

GameRegistry.addShapelessRecipe(ItemStack output, Object... params)

ItemStack output : 조합법의 결과인 ItemStack입니다.

Object... params : 조합법의 재료인 다수의 ItemStack입니다.

output의 아이템 스택은 개수가 반영됩니다. 하지만 params의 아이템 스택은 개수가 반영되지 않습니다. 결과는 코드를 통해 살펴보겠습니다.

    @Mod.EventHandler

    public static void preInit(FMLPreInitializationEvent event) {

        GameRegistry.addShapelessRecipe(new ItemStack(Items.bread, 2), new ItemStack(Items.wheat), new ItemStack(Items.wheat), new ItemStack(Items.sugar));

    }

위의 코드는 다음과 같은 조합법을 추가합니다.

그림 4 추가된 무형 조합법(Shapeless Recipe)

3. 화로 조합법(Smelting Recipe) 추가하기

화로 조합법의 구성요소는 재료 1개와 결과 1개입니다. 따라서 무형 조합법보다 추가가 간단합니다. 사실 무형 조합법에 없는 요소도 있는데, 바로 아이템을 구웠을 때 지급되는 경험치입니다. 즉, 화로 조합법에는 재료, 결과와 함께 경험치 양을 지정해야 합니다.

화로 조합법 또한 무형 조합법과 동일하게 Pre-Initialization에서 추가하는 것이 좋습니다.

화로 조합법은 다음 함수를 통해 추가합니다.

GameRegistry.addSmelting(ItemStack input, ItemStack output, float xp)

ItemStack input : 조합법의 재료가 되는 ItemStack입니다. Item과 Block이 직접 사용될 수도 있습니다.

ItemStack output : 조합법의 결과가 되는 ItemStack입니다.

float xp : 지급되는 경험치 양입니다.

예시를 통해 코드가 어떻게 적용되는지 확인해보도록 하겠습니다.

    @Mod.EventHandler

    public static void preInit(FMLPreInitializationEvent event) {

        GameRegistry.addSmelting(Items.wheat, new ItemStack(Items.bread), 1.0F);

    }

그림 5 추가된 화로 조합법

이로서 화로 조합법도 추가해보았습니다.

1. 게임 내에서 아이템 스택

아이템 스택은 모드를 개발함에 있어 아주 중요한 기본 개념입니다. 쉽게 말하면 가방의 한 칸을 차지하는 대상이 모두 아이템 스택입니다. 같은 아이템(Item)이라고 할 지라도 서로 개수가 다를 수 있고, 도구처럼 내구도나 부여된 마법이 다를 수도 있습니다. 이런 요소들이 모여 하나의 아이템 스택을 정의합니다.

그림 1 서로 다른 아이템 스택

위의 그림에서 첫 번째 줄은 내구도가 다른 도구와 부여된 마법(Enchantment)이 다른 도구를 보여줍니다. 내구도는 damage라는 변수에 값이 저장이 되고 마법 부여의 경우는 NBT라는 특수한 형태로 값이 저장됩니다. 두 번째 줄에서 블록도 아이템 스택이 될 수 있음을 확인할 수 있습니다. 세 번째 줄에서처럼 아이템 스택에는 stacksize라는 개수 정보를 가지고 있는 변수도 있습니다. 마지막 줄의 염료들은 서로 같은 아이템(Item) 종류입니다. 다만 이들 모두 damage 값이 달라서 그에 따라 완전히 다른 아이템으로 분류됩니다. 이런 아이템들은 하위 아이템(sub item)이라고 부릅니다.

2. 프로그램 내에서 아이템 스택

프로그램 내에서 이러한 아이템 스택을 생성하는 법을 간략하게 소개하겠습니다. 위에서 살펴본 아이템 스택의 요소 중에 가장 중요한 것은 "아이템(Item) 종류", "개수(stack size)", "내구도(damage)"입니다. ItemStack의 생성자도 위의 세 변수를 기본적으로 받아들입니다.

public ItemStack(Item item, int stacksize, int damage)

public ItemStack(Item item, int stacksize) : damage=0으로 설정

public ItemStack(Item item) : stacksize=1, damage=0으로 설정

생성자는 크게 3가지가 이용됩니다. 세 성분 모두 받는 생성자부터 아이템 종류만 받는 생성자까지 모두 사용할 수 있습니다. 인자를 1개나 2개만 받는 생성자는 개수(stacksize)나 내구도(damage)를 정해진 기본값으로 이용하기 때문에 정확히 어떤 값으로 설정되는지 상기하시기 바랍니다. 마인크래프트에서 미리 정의된 아이템 종류는 Items 클래스 내부에서 찾을 수 있습니다.

원본 게시글 주소:

Minecraft Forum

우리들의 마인크래프트 공간


아바스트로(Abastro)님과 우마공 개발팀으로서 제작한 모드입니다.


GLSL Shaders Mod와 Sonic Ether's Unbelievable Shaders를 사용했습니다.


F10(기본 설정, 수정 가능)을 누르면 최근에 찍은 스크린샷을 간략하게 확인할 수 있습니다. 오버레이(Overlay)이기 때문에 게임은 정상적으로 진행할 수 있습니다.


최근에 찍은 스크린샷이 위에 옵니다.


F10을 다시 누르면 목록이 확장되면서 모든 스크린샷을 볼 수 있습니다. 빨간 X를 누르면 스크린샷이 삭제됩니다. (실제 파일도 삭제됩니다)


사진이 굉장히 많을 경우, 아래의 스크롤을 이용해서 오래된 스크린샷도 확인할 수 있습니다.


스크린샷에 메모를 할 수도 있습니다. 아무거나 클릭하시면,

이런 창이 뜨면서 수정할 수 있습니다. 엔터를 눌러야만 내용이 저장되고, 빠르게 2번 누르면 저장이 되면서 꺼집니다. 저장된 상태에서는 좌우 화살표를 이용해서 다음 사진으로 넘어갈 수 있습니다.

본 강의는 Forge-1.7.10-10.13.4.1492-1.7.10의 환경에서 작성되었습니다.

1. 강의 목표

- 새로운 포션을 만든다.

- 새로운 포션에 적절한 아이콘을 지정한다.

- 새로운 포션에 원하는 효과를 지정한다.

- 새로운 포션을 엔티티에게 적용시킨다.

 

2. 사전 요구 사항

- 포지(Forge)

- 기초 모드 제작 환경(Base Mod File)

 

3. 포션이란

마인크래프트에서 포션이라고 하면 물약을 떠올리는 사람이 많습니다. 이 강의에서 포션은 정확히는 물약을 마셨을 때 적용되는 효과를 의미합니다. 이러한 효과는 무기에 특별한 능력을 부여하거나 대상의 능력치를 변화시키기도 합니다.

 

4. 포션 등록하기(Registration)

가장 먼저 할 일은 포션의 틀을 만들고 등록하는 것입니다. 일단 모드 파일이 다음처럼 설정되어있다고 가정하겠습니다.

 

package tutorial;

 

import cpw.mods.fml.common.Mod;

import cpw.mods.fml.common.SidedProxy;

import cpw.mods.fml.common.event.FMLInitializationEvent;

import cpw.mods.fml.common.event.FMLPostInitializationEvent;

import cpw.mods.fml.common.event.FMLPreInitializationEvent;

 

@Mod(modid = "tutorial", name = "Tutorial", version = "Test Version")

public class Tutorial {

 

    @SidedProxy(clientSide = "tutorial.ClientProxy", serverSide = "tutorial.CommonProxy")

    public static CommonProxy proxy;

 

    @Mod.EventHandler

    public static void preInit(FMLPreInitializationEvent event) {

    }

 

    @Mod.EventHandler

    public static void Init(FMLInitializationEvent event) {

    }

 

    @Mod.EventHandler

    public static void postInit(FMLPostInitializationEvent event) {

    }

 

}

 

 

1) 바닐라 포션 등록 체계

첫 번째로 바닐라 마인크래프트에서 어떻게 포션을 등록하고 다루는 지 살펴보도록 합시다.

 

package net.minecraft.potion;

 

import com.google.common.collect.Maps;

import cpw.mods.fml.relauncher.Side;

import cpw.mods.fml.relauncher.SideOnly;

import java.util.Iterator;

import java.util.Map;

import java.util.UUID;

import java.util.Map.Entry;

import net.minecraft.entity.EntityLivingBase;

import net.minecraft.entity.SharedMonsterAttributes;

import net.minecraft.entity.ai.attributes.AttributeModifier;

import net.minecraft.entity.ai.attributes.BaseAttributeMap;

import net.minecraft.entity.ai.attributes.IAttribute;

import net.minecraft.entity.ai.attributes.IAttributeInstance;

import net.minecraft.entity.player.EntityPlayer;

import net.minecraft.util.DamageSource;

import net.minecraft.util.StringUtils;

 

public class Potion

{

/** The array of potion types. */

public static final Potion[] potionTypes = new Potion[32];

public static final Potion field_76423_b = null;

public static final Potion moveSpeed = (new Potion(1, false, 8171462)).setPotionName("potion.moveSpeed").setIconIndex(0, 0).func_111184_a(SharedMonsterAttributes.movementSpeed, "91AEAA56-376B-4498-935B-2F7F68070635", 0.20000000298023224D, 2);

public static final Potion moveSlowdown = (new Potion(2, true, 5926017)).setPotionName("potion.moveSlowdown").setIconIndex(1, 0).func_111184_a(SharedMonsterAttributes.movementSpeed, "7107DE5E-7CE8-4030-940E-514C1F160890", -0.15000000596046448D, 2);

public static final Potion digSpeed = (new Potion(3, false, 14270531)).setPotionName("potion.digSpeed").setIconIndex(2, 0).setEffectiveness(1.5D);

public static final Potion digSlowdown = (new Potion(4, true, 4866583)).setPotionName("potion.digSlowDown").setIconIndex(3, 0);

public static final Potion damageBoost = (new PotionAttackDamage(5, false, 9643043)).setPotionName("potion.damageBoost").setIconIndex(4, 0).func_111184_a(SharedMonsterAttributes.attackDamage, "648D7064-6A60-4F59-8ABE-C2C23A6DD7A9", 3.0D, 2);

public static final Potion heal = (new PotionHealth(6, false, 16262179)).setPotionName("potion.heal");

public static final Potion harm = (new PotionHealth(7, true, 4393481)).setPotionName("potion.harm");

public static final Potion jump = (new Potion(8, false, 7889559)).setPotionName("potion.jump").setIconIndex(2, 1);

public static final Potion confusion = (new Potion(9, true, 5578058)).setPotionName("potion.confusion").setIconIndex(3, 1).setEffectiveness(0.25D);

/** The regeneration Potion object. */

public static final Potion regeneration = (new Potion(10, false, 13458603)).setPotionName("potion.regeneration").setIconIndex(7, 0).setEffectiveness(0.25D);

public static final Potion resistance = (new Potion(11, false, 10044730)).setPotionName("potion.resistance").setIconIndex(6, 1);

/** The fire resistance Potion object. */

public static final Potion fireResistance = (new Potion(12, false, 14981690)).setPotionName("potion.fireResistance").setIconIndex(7, 1);

/** The water breathing Potion object. */

public static final Potion waterBreathing = (new Potion(13, false, 3035801)).setPotionName("potion.waterBreathing").setIconIndex(0, 2);

/** The invisibility Potion object. */

public static final Potion invisibility = (new Potion(14, false, 8356754)).setPotionName("potion.invisibility").setIconIndex(0, 1);

/** The blindness Potion object. */

public static final Potion blindness = (new Potion(15, true, 2039587)).setPotionName("potion.blindness").setIconIndex(5, 1).setEffectiveness(0.25D);

/** The night vision Potion object. */

public static final Potion nightVision = (new Potion(16, false, 2039713)).setPotionName("potion.nightVision").setIconIndex(4, 1);

/** The hunger Potion object. */

public static final Potion hunger = (new Potion(17, true, 5797459)).setPotionName("potion.hunger").setIconIndex(1, 1);

/** The weakness Potion object. */

public static final Potion weakness = (new PotionAttackDamage(18, true, 4738376)).setPotionName("potion.weakness").setIconIndex(5, 0).func_111184_a(SharedMonsterAttributes.attackDamage, "22653B89-116E-49DC-9B6B-9971489B5BE5", 2.0D, 0);

/** The poison Potion object. */

public static final Potion poison = (new Potion(19, true, 5149489)).setPotionName("potion.poison").setIconIndex(6, 0).setEffectiveness(0.25D);

/** The wither Potion object. */

public static final Potion wither = (new Potion(20, true, 3484199)).setPotionName("potion.wither").setIconIndex(1, 2).setEffectiveness(0.25D);

public static final Potion field_76434_w = (new PotionHealthBoost(21, false, 16284963)).setPotionName("potion.healthBoost").setIconIndex(2, 2).func_111184_a(SharedMonsterAttributes.maxHealth, "5D6F0BA2-1186-46AC-B896-C61C5CEE99CC", 4.0D, 0);

public static final Potion field_76444_x = (new PotionAbsoption(22, false, 2445989)).setPotionName("potion.absorption").setIconIndex(2, 2);

public static final Potion field_76443_y = (new PotionHealth(23, false, 16262179)).setPotionName("potion.saturation");

Potion 클래스의 일부

 

블록과 아이템과 비슷하게 Potion 클래스의 객체들이 미리 생성되어 있습니다. 각 포션에는 고유한 정수(int)값인 id가 있습니다. 이 id는 포션 객체가 저장되는 배열(potionTypes)에서 주소(index)를 의미합니다.

 

2) 포션 등록 체계 수정

 

여기서 주목 할 점은 마인크래프트에서 선언된 배열의 크기가 32이고 23까지 이미 기존 포션들이 사용 중이란 것입니다. 포지(Forge) 위에서 자신의 모드 이외에 다른 모드도 충분히 포션을 추가할 여지가 있으므로 부족한 배열 공간으로 인해 충돌이 일어날 수 있습니다. 따라서 우리는 1) 배열상에 비어있는 공간을 찾고, 2) 없다면 배열을 늘려야 합니다.

 

(1) 배열 상에서 사용 가능한 주소 찾기

다음의 함수(Method)는 Potion.potionTypes를 탐색하여 비어있는 공간을 찾아서 해당 주소를 반환해줍니다. 만약 빈 공간이 없다면 -1을 반환합니다. 탐색을 1부터 시작하는 이유는 바닐라 마인크래프트에서도 0은 비워놓기 때문입니다. 실제 /effect 명령어도 0은 받아들이지 않습니다.

 

    public static int getEmptyID() {

        for (int i = 1 ; i < Potion.potionTypes.length; i++) {

            if (Potion.potionTypes[i] == null) return i;

        }

        return -1;

    }

 

(2) 배열 확장하기

이제 하나 남은 과정은 배열 상에 남은 공간이 없는 경우 Potion.potionTypes를 더 큰 배열로 바꾸는 것 입니다. Potion.potionTypes가 final로 정의되어 있기 때문에 일반적인 방법으로는 불가능합니다. 따라서 Java Reflection를 사용하여 코드를 작성하겠습니다.

 

    public static void expand() {

        Potion[] potionTypes = null;

 

        for (Field f : Potion.class.getDeclaredFields()) {

            f.setAccessible(true);

            try {

                if (f.getName().equals("potionTypes")

                        || f.getName().equals("field_76425_a")) {

                    Field modfield = Field.class.getDeclaredField("modifiers");

                    modfield.setAccessible(true);

                    modfield.setInt(f, f.getModifiers() & ~Modifier.FINAL);

 

                    potionTypes = (Potion[]) f.get(null);

                    final Potion[] newPotionTypes = new Potion[Potion.potionTypes.length*2];

                    System.arraycopy(potionTypes, 0, newPotionTypes, 0,

                            potionTypes.length);

                    f.set(null, newPotionTypes);

                }

            } catch (Exception e) {

            }

        }

    }

Ref: http://www.minecraftforum.net/forums/archive/tutorials/931752-forge-creating-custom-potion-effects

 

위의 함수는 Potion.potionTypes의 크기를 2배로 늘리는 함수 입니다. Reflection을 알면 이해에 큰 차질이 없을 것이고 그렇지 않다면 설명하기 어려우므로 생략하도록 하겠습니다.

 

3) 포션 클래스와 객체 만들기

이제 새로운 포션을 등록하기 위한 기초가 다져졌습니다. 이제는 등록할 포션 객체를 만들 차례입니다.

 

package tutorial;

 

import net.minecraft.potion.Potion;

 

public class PotionTutorial extends Potion {

    public PotionTutorial(int id, boolean isBadEffect, int color) {

        super(id, isBadEffect, color);

        setPotionName("potion.timeslow");

    }

}

 

위와 같이 간단하게 새로운 포션 클래스를 만드세요. setPotionName에는 적당한 이름을 지정해주시면 됩니다. .lang파일을 이용하면 언어마다 다른 이름을 지정해줄 수 있습니다. 현재 단계는 포션을 등록만 하는 과정이므로 구체적인 내용은 작성하지 않고 필요한 내용만 작성합니다.

 

 

그 다음 포션 객체를 선언합시다. 주 Mod 클래스에 만들겠습니다. 다음과 같이 해야 필요할 때, 배열이 확장됩니다.

 

    public static Potion potionTutorial;

 

@Mod.EventHandler

    public static void preInit(FMLPreInitializationEvent event) {

        int id = getEmptyID();

        if (id != -1) {

            potionTutorial = new PotionTutorial(getEmptyID(), false, 0xFFFFFF);

        } else {

            expand();

            potionTutorial = new PotionTutorial(getEmptyID(), false, 0xFFFFFF);

        }

    }

 

 

위의 코드를 이해하기 위해 포션(Potion) 클래스의 생성자를 설명하겠습니다.

 

protected Potion(int p_i1573_1_, boolean p_i1573_2_, int p_i1573_3_)

int p_i1573_1_,

포션의 id입니다

boolean p_i1573_2_

포션이 해로운 효과인지를 나타냅니다. 해로운 효과면 포션이 약화됩니다.

int p_i1573_3_

포션의 색 입니다. 입자 색을 결정합니다. 0xFFFFFF는 16진법으로, 흰색을 의미합니다.

 

 

5. 아이콘 지정하기

모든 포션은 아이콘을 가지고 있습니다. 포션 효과가 아이콘을 가지는 이유는 인벤토리에서 표시되기 때문입니다. 포션 아이콘 하나는 18*18의 크기를 가지고 있습니다.

 

1) 바닐라 포션 아이콘

 

마인크래프트 내부에 기본적으로 존재하는 포션 아이콘들입니다. 위의 배열을 보시면 Potion 클래스에서 선언된 포션 객체들의 ".setIconIndex(6, 0)" 함수가 이해되실 겁니다.

 

2) 포션 아이콘 만들기

포션 아이콘의 규격을 알았으니 이제 아이콘 하나를 만들어 보겠습니다. 크기는 18*18입니다.

 

3) 포션 아이콘 렌더링

제작한 포션 아이콘을 렌더링하려면 조금의 작업이 더 필요합니다. PotionTutorial 클래스로 가봅시다.

 

    ResourceLocation texture = new ResourceLocation("tutorial","textures/potions/potiontutorial.png");

    

    public PotionTutorial(int id, boolean isBadEffect, int color) {

        super(id, isBadEffect, color);

        setPotionName("potion.timeslow");    }

    

    @Override

    public void renderInventoryEffect(int x, int y, PotionEffect effect, Minecraft mc) {

        mc.getTextureManager().bindTexture(texture);

        drawTexturedRect(x+6, y+7, 16, 16);

    }

    

    public static void drawTexturedRect(int left, int up, int width, int height) {

Tessellator tessellator = Tessellator.instance;

tessellator.startDrawingQuads();

tessellator.addVertexWithUV((double)left, (double)(up + height), 0.0D, 0.0D, 1.0D);

tessellator.addVertexWithUV((double)(left + width), (double)(up + height), 0.0D, 1.0D, 1.0D);

tessellator.addVertexWithUV((double)(left + width), (double)up, 0.0D, 1.0D, 0.0D);

tessellator.addVertexWithUV((double)left, (double)up, 0.0D, 0.0D, 0.0D);

tessellator.draw();

    }

 

위 처럼 ResourceLocation을 지정하고, 인벤토리에서 아이콘을 그리는 함수인 renderInventoryEffect 함수를 오버라이드하면 포션이 그려져야 할 때, 저 함수가 호출됩니다.

 

지정한 ResourceLocation에 방금 만든 파일을 복사하여 옮겨 놓습니다. ResourceLocation의 생성자에 대해 간략히 설명하자면 첫 번째 인자는 Mod ID이고, 두 번째 인자가 상대 경로입니다.

 

 

6. 포션에 효과 설정하기

포션 효과는 정말 다양할 수 있습니다. 그렇기 때문에 능력치를 수정하는 것뿐만 아니라 더 다양한 것을 할 수 있도록 performEffect(EntityLivingBase entity, int level) 함수가 Potion 클래스에 구현되어 있습니다. 우리가 할 것은 이 함수를 오버라이드하여 원하는 효과를 작성하는 것입니다.

 

    @Override

    public void performEffect(EntityLivingBase entity, int level) {

 

    }

 

    @Override

    public boolean isReady(int duration, int level) {

        return true;

    }

 

하나 더 눈여겨 보셔야 할 부분은 isReady 함수입니다. 이 함수가 true를 반환해야만 performEffect 함수가 실행됩니다.

 

 

대상 엔티티(Entity) 근처의 투사체를 느리게 하는 효과를 작성해보겠습니다.

 

    @Override

    public void performEffect(EntityLivingBase entity, int level) {

        double radius = 8;

        double x = entity.posX;

        double y = entity.posY;

        double z = entity.posZ;

        List projectiles = entity.worldObj.getEntitiesWithinAABB(IProjectile.class, entity.boundingBox.expand(radius, radius, radius));

        for (Object i : projectiles) {

            Entity proj = (Entity)i;

            proj.motionX*=0.5;

            proj.motionY*=0.5;

            proj.motionZ*=0.5;

        }

    }

 

저 코드에 대한 설명은 생략하겠습니다. 지금은 포션을 추가하는 예시를 보여주는 것이지, 어떻게 엔티티(Entity)를 검출하고 다루는 지 보는 강의는 아니니까요.

 

 

7. 포션 효과를 엔티티에게 적용시키기

접근 가능한 한 엔티티(Entity)에게 포션 효과를 적용시키는 명령어는 다음과 같습니다.

 

        entity.addPotionEffect(new PotionEffect(Tutorial.potionTutorial.id, 6000, 1, false));

 

PotionEffect의 생성자에 대해 설명하자면 첫 인자는 대상 포션의 id, 두 번째 인자는 포션 효과가 적용될 기간(tick), 그리고 마지막은 신호기(Beacon)에서 비롯된 효과인지를 나타내는 인자입니다.

 

 

위의 코드를 실행시킬 상황을 만들자면 또 한참 복잡하니까, 그것은 여러분의 몫으로 남겨 두겠습니다.

 

 

8. 시험

이제 포션 코드를 실험해보겠습니다. 엔티티(Entity)에게 포션 효과 적용시키는 코드가 없으므로 명령어를 이용하겠습니다. 이 경우 포션 ID를 직접 알아야 하는데, 포션 등록 과정에서 콘솔로 출력하셔도 좋고 아니면 24를 넣어보세요. 바닐라 포션 바로 다음 ID이기 때문에 24번으로 할당 되었을 가능성이 큽니다.

 

 

 

이제 근처에 스켈레톤을 소환해서 투사체가 느려지는 지 확인해보겠습니다.

 

 

동영상이었다면 더욱 효과를 알아보기 쉬웠겠지만 사진도 충분할 것 같습니다. 화살이 오다가 땅으로 향하는 걸 확인할 수 있으실 겁니다.

 

 

 

인벤토리에서도 아이콘이 정상적으로 나타납니다. 시간은 너무 길게 설정해서 저렇게 표시되네요. 이름은 .lang 파일을 만드셔야 정상적으로 표시됩니다.

 

이제 포션 효과를 추가하는데 필요한 모든 것들을 다루어보았습니다.

 

 

질문은 언제나 받습니다. 댓글 달아주세요.

원본 주소는 다음입니다.

http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/modification-development/2425861-developing-with-other-mods-in-your-environment-for

 

 

배고픈 동물(Hungry Animals) 모드를 개발하면서 다른 모드와의 호환을 개선하기 위해 개발 환경에 다른 모드를 넣을 필요가 생겼습니다. 기본적으로는 넣고 싶은 모드를 디컴파일(decompile)해서 넣는 게 원칙이지만, 모든 모드가 디컴파일 된 버전을 지원하는 것이 아니기에 어려움이 있었습니다.

다행히도 ChickenBonesCore가 디컴파일이 되지 않은 jar 파일도 개발 환경에 추가할 수 있는 기능을 제공합니다. 그 기능을 쓰기 위해서는 다음의 과정을 따라야합니다.

1. ChickenBonesCore를 eclipse/mods에 넣습니다

2. 프로젝트를 실행하면 "Select an mcp conf dir for the deobfuscator"라는 제목을 가진 대화 상자가 나타납니다.

3. 윈도우 탐색기에서

...\Users\[name]\.gradle\caches\minecraft\net\minecraftforge\forge\[forge_version]\unpacked\conf 로 이동합니다.

4. "packaged.srg"를 저기서 복사한 후

...\Users\[name]\.gradle\caches\minecraft\de\oceanlabs\mcp\mcp_snapshot\[version_date]\ 로 복사합니다.

5. 다시 2번에서 나타났던 대화 상자로 돌아가서

...\Users\[name]\.gradle\caches\minecraft\de\oceanlabs\mcp\mcp_snapshot\[version_date]\ 를 선택합니다.

이렇게만 하면 eclipse/mods에 있는 다른 모드도 바로 개발 환경에 적용할 수 있습니다.

새로운 엔티티(Entity)를 만들게 되면 Attribute를 다루게 됩니다. Attribute는 최대 체럭, 공격력, 이동 속도와 같은 엔티티(Entity)의 능력치를 말합니다. Attribute가 정확히 무엇인가하는 더욱 자세한 설명은 마인크래프트 위키에 잘 나와있습니다. http://minecraft.gamepedia.com/Attribute

 

이 강의에서는 기존의 Attribute를 다루고, 직접 새로운 Attribute를 정의하고 더 나아가서 코드로 그 값들을 수정하는 법을 배울 것입니다.

 

Attribute 만들기

1단계

새로운 Attribute를 만들기 위해 가장 먼저 해야 할 일은 IAttribute 객체를 만드는 것입니다. 인터페이스(Interface) 객체를 만들어야 한다고 해서 IAttribute를 상속하는 클래스(Class)를 만들 필요는 없습니다. 이미 마인크래프트(Minecraft)에서 기존의 모든 Attribute에 적용되고 있는 RangedAttribute라는 클래스(Class)를 제공하기 때문입니다. 생성자는 다음과 같은 인자를 가지고 있습니다.

 

RangedAttribute(IAttribute p_i45891_1_, String p_i45891_2_, double p_i45891_3_, double p_i45891_5_, double p_i45891_7_)

IAttribute
p_i45891_1 :
정확한
기능을 파악할 없습니다. 모든 Attribute에서 "(IAttribute)null" 지정합니다.
String p_i45891_2_ : Attribute
이름입니다. 이름은 같은 Attribute 같은 엔티티(Entity)에게 2 이상 지정하지 못합니다. 그렇기 때문에 모드 아이디(Mod ID) 이용해서 구분하는 것이 바람직합니다. SoundBody.MOD_ID + ".digspeed"」와 같은 형식입니다.
double p_i45891_3_ : Attribute의 기본값입니다. 엔티티(Entity)가 처음 생성될 때, 기본 값으로 이 Attribute에 대입됩니다. 최소값, 최대값의 범위를 벗어날 수 없습니다. 만약 벗어난다면 IllegalArgumentException을 발생시킵니다.
double p_i45891_5_ : Attribute의 최소값입니다. 대부분 0.0D의 값을 가지고 있습니다. 최소값이기 때문에 실제 Attribute 값은 이 이하로 내려가지 않습니다. 이 값 자체가 최대값보다 커진다면 IllegalArgumentException을 발생시킵니다.
double p_i45891_7_ : Attribute의 최대값입니다. 대부분 Double.MAX_VALUE의 값을 가지고 있습니다. 최대값이기 때문에 실제 Attribute 값은 이보다 커질 수 없습니다. 이 값 자체가 최소값보다 작아진다면 IllegalArgumentException을 발생시킵니다.

 

Attribute를 만드는 것은 크게 어렵지 않습니다.

public static IAttribute digspeedFactor = new RangedAttribute((IAttribute)null, Strings.attribute_digspeedfactor_name, 1.0, 0.0, Double.MAX_VALUE);

 

하지만 이게 끝은 아닙니다. 만약 Attribute가 클라이언트(Client)와 동기화가 되어야 한다면 setShouldWatch()를 true로 설정해야만 합니다. false로 한다면 당연히 동기화 기능을 끌 수 있습니다. 이 함수는 값이 변경된 객체를 다시 반환하기 때문에 다음과 같이 코드를 작성할 수 있습니다.

public static IAttribute digspeedFactor = new RangedAttribute((IAttribute)null, Strings.attribute_digspeedfactor_name, 1.0, 0.0, Double.MAX_VALUE).setShouldWatch(true);

 

 

2단계

열심히 Attribute를 만들었지만 사실 엔티티(Entity)는 그런 Attribute가 있다는 사실조차 알지 못합니다. 그렇기 때문에 Attribute를 반드시 등록해야 합니다. 등록하는 시기는 엔티티(Entity) 클래스(Class)에서 applyEntityAttributes() 함수가 호출될 때입니다. 간단히 override하시면 됩니다. 그리고 this.getAttributeMap().registerAttribute(MY_OWN_ATTRIBUTE)을 호출하세요. 물론 「MY_OWN_ATTRIBUTE」는 그저 예시일 뿐입니다. 1단계에서 만든 Attribute를 등록하시면 될 겁니다.

@Override

protected void applyEntityAttributes() {

super.applyEntityAttributes(); // 상위 함수를 호출하지 않으면 체력이나 이동속도와 같은 기본적인 Attribute가 등록되지 않을 것입니다.

this.getAttributeMap().registerAttribute(TurretAttributes.MAX_AMMO_CAPACITY);

}

 

 

3단계

Attribute를 만들고 등록까지 마쳤지만 아직 쓰임은 없습니다. 이 값을 가져오기는 상당히 간단합니다. this.getEntityAttribute(MY_OWN_ATTRIBUTE).getAttributeValue()를 호출하시면 값을 가져올 수 있습니다.

 

 

 

Modifier 만들기

이제 Attribute 값을 수정만 하면 필요한 기능을 구현할 수 있을 것입니다.

1. 기본값 변경하기

이 방법은 직접적으로 값을 변경하기 때문에 간단합니다. 최대 체력을 직접 지정해주거나 할 때 많이 사용되는 방법입니다. 다음의 코드를 참고하세요.

this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(40.0D); // 엔티티(Entity)의 최대 체력을 40(하트 반 칸)으로 지정합니다.

이 방법은 클라이언트(Client)와 자동으로 동기화되지 않습니다. 동기화 하려면 수동적으로 패킷(Packet)을 보내서 하셔야 합니다.

 

 

2. Attribute에 Modifier 추가하기

이 방법은 Attribute를 바꾸고 나서 언제든지 원래 값으로 되돌릴 수 있는 '가역적'인 방법입니다. 우리가 추가하려는 것은 기본적으로 Attribute Modifier이고 역시 위키에 잘 설명되어 있습니다. http://minecraft.gamepedia.com/Attribute#Modifiers

Attribute랑 비슷하게 객체를 만들어야 합니다. 이 경우에도 이미 AttributeModifier 클래스(Class)가 정의되어 있어 편리합니다. 생성자의 인자는 다음과 같습니다.

 

public AttributeModifier(UUID p_i1606_1_, String p_i1606_2_, double p_i1606_3_, int p_i1606_5_)

 

UUID p_i1606_1_ : UUID입니다. 그렇기 때문에 Modifier마다 유일해야 합니다. Attribute에서 이름(name)에 해당하는 값인 것이죠. https://www.uuidgenerator.net/ 에서 UUID를 생성할 수 있습니다. 그냥 이렇게 생성해도 겹칠 일은 거의 없습니다. 이게 UUID 클래스(Class) 의 편리함입니다. UUID.fromString()를 이용하면 사전에 생성 해 놓은 문자열로부터 UUID를 얻을 수 있습니다. UUID.fromString("e6107045-134f-4c54-a645-75c3ae5c7a27")」이런 식 입니다.
String p_i1606_2_ : Modifier 이름에 해당하는 인자입니다. 빈 문자열만 아니면 문제가 없습니다. 심지어 겹쳐도 됩니다. 어차피 식별에 사용되는 인자는 UUID이기 때문입니다.
double p_i1606_3_ : 얼마나 값을 변경할 것인지에 해당하는 인자입니다. Modifier를 통해서도 Attribute의 기본적인 최소, 최대값은 넘을 수 없으니 유의하셔야 합니다.
int p_i1606_5_ : Modifier의 연산자(Operation)입니다. 위키 내용을 참고하세요. 간단하게 설명하면 연산자(Operation)는 어떻게 값이 변경되는지를 결정하는 요소입니다. Attribute에 값을 더할 건지, 곱할 건지를 지정하는 것입니다.

연산자(Operation) 0: X를 만큼 더합니다. 연산자(Operation) 1: Y가 "X*"만큼 증가합니다. 연산자(Operation) 2: Y=Y*(1+). 처음 "X=기본값"으로 대입됩니다. 그 후 연산자(Operation) 0이 실행이 되고 "Y=X"로 대입됩니다. 이후 연산자(Operation) 1이, 마지막으로 연산자(Operation) 2가 적용됩니다.

 

이제 모든 인자를 알아보았으니 객체를 생성해 보겠습니다.

MY_CUSTOM_MODIFIER = new AttributeModifier(UUID.fromString("e6107045-134f-4c54-a645-75c3ae5c7a27"), "myCustomModifier420BlazeIt", 0.360D, EnumAttrModifierOperation.ADD_PERC_VAL_TO_SUM.ordinal());

이제 객체도 만들었으니 어떻게 적용하고 해제하는지 확인 해보겠습니다.

entity.getEntityAttribute(MY_ATTRIBUTE_INSTANCE).applyModifier(MY_CUSTOM_MODIFIER);
entity.getEntityAttribute(MY_ATTRIBUTE_INSTANCE).removeModifier(MY_CUSTOM_MODIFIER);

 

 

 

Attribute는 자동으로 저장되고 불러와집니다. 따라서 수동적으로 해당 작업을 해주실 필요는 없습니다. 다만 클라이언트(Client)와 동기화만 조금 신경 써 주시면 됩니다.

 

 

 

 

참고자료:

http://www.minecraftforge.net/forum/index.php/topic,30137.msg156378.html

위의 자료에서 강의 구성, 샘플 코드 등을 참고 했습니다. 사실상 거의 번역본입니다.

'마인크래프트 모딩 > 강의' 카테고리의 다른 글

아이템 스택(Item Stack)  (2) 2015.10.05
[모딩] 포션 추가  (0) 2015.08.15
5. AI  (5) 2015.02.06
4. 포션 추가(번역)  (0) 2015.02.05
3.동물을 추가해보자 (EntityAnimal)  (0) 2015.02.05

Major New Feature:
@Mode Side Control:
New @Mod properties to define which environment to load the mod on.
   clientSideOnly will only be loaded in the Client environment.
   serverSideOnly will only be loaded in the Dedicated server environment.
   Combine with acceptedMinecraftVersions to prevent users from loading the mod in the incorrect environment.
   For the love of god modders USE THIS! It'll stop all those 'you ran a 1.7 mod on 1.8!' and 'you ran a client mod on the server!' 

@Mode 환경(서버/클라이언트) 제어:

새로운 @Mod properties를 설정해서 서버/클라이언트 중 어디에서 모드가 실행되어야 하는지 지정합니다. 종전의 acceptedMinecraftVersions와 함께 사용한다면 사용자가 잘못된 환경에서 모드를 실행하는 것을 막을 수 있습니다. 그러니까 모드 제작자들은 이 기능을 적극적으로 사용하세요.


Command Exploit:
Fixed several exploits that would allow players to run commands above their privledge level.

명령어(Command) 버그 악용:

권한을 무시하고 명령어(Commands)를 실행할 수 있었던 버그를 수정했습니다.


Downgrading Netty:
The version of netty shipped with the vanilla dedicated server jar contains bugs. So Forge downgrades it to match the version of Netty that the client uses.

Netty 버전 낮춤(Downgrade)

바닐라 서버 파일에 사용되는 Netty가 버그를 가지고 있었습니다. 따라서 Forge에서 그 버전을 클라이언트 파일에 사용되는 것과 같은 것으로 낮췄습니다.

BlockState.json format change:
Modders have been whining seince day one about the new json model format that was introduced in 1.8. They have some valid points that it is quite verbose. Which is what Forge is here to address! {Seriously guys, working forward is better then bitching}
See https://github.com/MinecraftForge/MinecraftForge/pull/1885 for more details.

BlockState.json 형식 변경:

모드 제작자들은1.8에 새로 생긴 json 모델 형식 때문에 고통 받고 있었습니다. 새로 생긴 모델 형식은 나쁘진 않지만 너무 귀찮았죠. (진지하게 이야기하는데, 열심히 작업하는게 욕하는 것 보단 좋습니다) 자세한 사항은 https://github.com/MinecraftForge/MinecraftForge/pull/1885를 보세요

B3D Improvements:
 - fixed keyframe transformation application
 - textures are now resolved the same way as in vanilla models
 - added the ability to use forge blockstate texture information
 - removed unused code from the B3D example 

B3D 개선

(B3D에 대해 아는 지식이 전무합니다)


Loading Screen:
Thanks to Fry for working on a new loading screen for Forge.
You can see it in action below, it works on 99% of end users computers, however some OSX/Linux graphics drivers, and some mod combinations do not behave with it correctly.
So, we have added the option to disable it. Simply go into .minecraft/config/splash.properties and set enabled=false.
If Forge detects one of the common errors it will automatically disable this entry and show a error message in the log stating that you should simply try running it again. But if all else fails you can manually disable it as previously stated.
It works with 99% of users, and it's far better then just a 'not responding' screen so it's enabled by default.

불러오기 화면

새로운 불러오기 화면을 만들어준 Fry에 감사의 인사를 전합니다.
아래의 화면은 99% 컴퓨터에서 모두 동작하지만 간혹 OSX/Linux 그래픽 드라이버에 특정한 모드 조합에서 동작하지 않습니다.
그래서 끌 수 있는 기능을 넣었습니다. .minecraft/config/splash.properties에서 enabled=false 로 설정하세요.
에러가 발생하게 되면은 Forge가 자동으로 인식해서 에러 메시지를 출력하고 해당 부분만 기능을 끄기 때문에 사용자는 재시작만 하면 됩니다. 하지만 불러오기 화면을 출력하는 기능 전체가 오작동한다면 앞서 이야기한대로 수동적으로 꺼줘야 합니다.
대부분의 환경에서 정상적으로 작동하고, '응답 없음' 화면보다는 훨씬 좋기 때문에 기본적으로 이 기능은 켜집니다.


FML and Forge Official Merge:
FML and Forge have always shipped together, but for my own sanity and easier maintainability FML and Forge are now in the same github repository. FML will no longer be shipped standalone because nobody ever used it. And eventually everything in FML will be merged directly into Forge. Code and packages will most likely stay the same so modders, don't worry! This is mainly just a ease of development {no longer having to push to 4 repositories every time I change FML}

FML과 Forge가 공식적으로 병합됩니다:

FML과 Forge는 항상 같이 나왔습니다. 하지만 순전히 저의 판단과 관리효율을 위해 FML과 Forge를 같은 깃허브(Github) 레포지토리(Repository)에 넣기로 결정했습니다. FML는 지금껏 아무도 이것만 따로 쓰지 않았기 때문에 이제 혼자서만 따로 발매되지 않습니다. 그렇기 때문에 FML에 있는 모든 것들은 Forge로 병합될 것입니다. 모드 제작자 여러분, 걱정마세요. 코드랑 패키지는 그대로 놔둘 것입니다. 이건 다 쉬운 개발을 위한 것이니까요.(FML을 바꿀때마다 4개의 레포지토리에 푸시해야만 했습니다.)

마인크래프트 1.8-11.14.3.1499-환경에서 제작되었습니다.

공식 게시글은 http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/2228763-hungry-animals-more-realistic-animals입니다.

다운로드는 http://adf.ly/1GD62S입니다.

공식 위키는 http://hungryanimals.wikia.com/wiki/Hungry_Animals_Wiki 입니다. 한국어 문서 작성을 도와주실 분이 있다면 감사하겠습니다.

 

1.0.4.2~2.0까지의 변경 사항 원본

2.0.beta07

1. Rendering

Pointed animal's hunger/health/age/taming and potion effects are displayed on mouse cursor.

Potion Effect : displays every effect of the pointed animal one by one.
Hunger : Blue line
Health : Green line
Age : Magenta (Adult, not reproducable), Yellow (Baby, growing), Black (otherwise)
Taming: Red (wild), Green (tamed)

 

2.0.beta06

1. Floor Cover

Floor Cover has a special effect for the animals above.
There are 4 kinds of floor covers with specific effect :

Leaf Floor Cover : baby animals grow faster (+25%)
Wool Floor Cover : adult animals produce babies faster (+25%)
Hay Floor Cover : excreta dissolves faster

2. Recipe

Recipe for floor covers are added.

3. Configuration

Erosion On Hay : probability that the excreta block above a hay floor cover block dissolves on update tick.

 

2.0.alpha05

1. Large Crank

Add recipe
Animals work when full (with more than 50% of hunger)

2. Debug Glass

internal improvement (Code Optimization)
GUI Improvement
can edit some variables of the animal

3. Millstone

not directly powered by player (by crank)
accepts items by right-click
shows items inside
*currently uncraftable

4. Bug Fix

Thresher/Blender drops internal items when destroyed.

 

2.0.alpha04

1. Large Crank

saves its leashed entity.
improved animal AI leashed.
produces energy depending on the animal's position. (produces more energy when the animal leads the connected handle of the crank)
only tamed animals can run large cranks

 

 

2.0.alpha03

1. Large Crank – WIP

animals run this crank : produces much more energy
multiblock structure like bed (X*Y*Z = 3*1*3)
leash animals to a large crank with leads

 

2.0.alpha02

1. Recipe

Trough: needs composite wood to craft

Thresher: needs composite wood to craft

Blender:

saltpeter+manure=6 bonemeal

carrot+patato=1 mixed feed

2. Configuration

Animal glue crafting is configurable.

(item, damage)=(number)

Every animals can eat mixed food as 80.0 saturation

 

 

2.0.alpha01

1. Composite Wood

crafted by two different kinds of woods and animal glue. It's used to make wooden machines.

A

B

C

A

B

C

A

B

C

A: wood log (all of A are same, different from C), B: animal glue, C: wood log (all of C are same, different from A)

2. Animal Glue

crafted by right-click a filled cauldron with bone, tendon or leather. (2 animal glue for bone, 4 animal glue for tendon, 6 animal glue for leather) This process will use a 1/3 water of the cauldron.

3. Machines have recipes. Most of them need composite wood.

items only for crafting : Composite Wood Casing, Blender Blade,

 

2.0.alpha00

1. Machines are added. No recipes for them until now.

1) Axle, Belt, Wheel : transports energy
2) Crank : produces energy by hand.
3) Thresher : threshes a wheat to seeds and straws.
4) Millstone : squeezes seeds.
5) Blender : mixes items to make something else.

2. Poppy : just plant onto a farmland to get poppy seeds. You can harvest poppy when it has a flower or harvest the seeds when mature.

3. Changes from 1.7.10

Trap Cover : Visual Improvement: 3D block model
Bola : In-hand animation Improvement: 3rd person view supported. Textures are updated.
Slingshot : Slingshot string has a texture. In-hand animation Improvement: 3rd person view supported.
Mutton : Similar changes like other meats. (3~6 without looting)

4. Configuration

1) byFood value changed :

Wheat Seed : 25.0 -> 20.0
Straw : 10.0
Poppy Seed : 20.0

2) In-game setting is temporally disabled.

3) Configuration files are in 'HungryAnimals' folder.

4) Configuration is divided into 3 files : Animal, World, Recipe

5) Syntax is changed

Excretion constant -> required hunger consumption to produce a pile of excreta
byFoodList : (item, damage) = (hunger)

6) Blender Recipe

(item, damage), (item, damage) = (item, number, damage)

5. Chisel and chiseled blocks – removed

6. Creative Tab is added : Every items are in Hungry Animals Tab.

7. Bug fix

1) Mod compatibility for item drops.

2) Mushrooms can be applied to only tamed animals.

1.0.4.2.beta015

- Texture of niter bed slightly changed.

- bug fixed

1) crash in trough

2) color of disease effect

1.0.4.2.beta014

- baby animals do not drop items.

- Animals sometimes wander.

- bug fixed :

1) taming value reduction when attacking animal

2) Unexpected consumption of hunger.

1.0.4.2.beta013

- Debug Glass is now useful..!

- Untamed animals eat food more frequently when hungry.

- Baby's taming value is the average of parents'.

1.0.4.2.beta012

- Trough : New name for food box. Icon also changed(special thanks to PitchBright). Rendering changed(almost same. But rendering error with optifine solved.).

- Sheep : produces wool every 5 mins. (needs hunger. Configurable)

1.0.4.2.beta011

- Debug Glass! : right click to animals to get hunger, taming value, excretion, ect…

- hunger consumption proportional to current movement speed is temporally removed.

- bug fixed (related to cow and pig's health)

- bug fixed (empty configuration tap for mooshroom)

- bug fixed (byBlock Rate configuration : animals try to eat expensive food first.)

- bug fixed (crash : animals)

- bug fixed (crash : foodbox)

1.0.4.2.beta010

- Excreta changed a little bit. :

Fermentation : slightly changed.

Fertilization : manure is dissolved first.

Erosion : excreta is dissolved first. And if it's covered, doesn't be eroded.

- bug fixed (related to excreta)

1.0.4.2.beta009

- Configuration System is developing : food/block and hunger configuration changed. Old configuration should be deleted before launch.

1.0.4.2.beta008

- Critical Error fixed.

- Food can only help growth baby animals(Green particle effect). (can't make baby more frequently J )

1.0.4.2.alpha007

- Milk can only be harvested when the cow is tamed (>= 1)

- FoodBox : Right click with time to put foods, with empty hand to take out. (Tamed animals(>= 1) will eat foods in foodbox. buggy)

1.0.4.2.alpha006

- Food Box is changing…. WIP

- Production of milk needs hunger. Cows need resting period to produce milk again. All of these can be configured.

1.0.4.2.alpha005

- Texture updated (FoodBox)

1.0.4.2.alpha004

- default fertilization(dissolve away on grass, dirt, sand) speed of excreta increased.

- The model of Food Box changed to 3D. Texture is currently poor. It becomes easy to put items into the food box.

- bug fixed ( courtship )

1.0.4.2.alpha003

- tall grass growth is slowed 1/3. Therefore hunger of grass is multiplied 3 (to 15).

- tall grass doesn't grow when there's other grass beside the block.

- block excreta's custom material added.

1.0.4.2.alpha002

- Compatibility increased EXTREMELY. However this change would cause slightly more lag.

- block-formed food list for animals is configurable.

1.0.4.2.alpha001

- Chickens don't eat grass. Honestly they break grass and get seeds to eat. Therefore chickens feeding activity becomes less efficient. Their population would be shrink.

- Animals' movement speed is now organized.

 

Base Factor

To Grass

To Food

To Mate

To Run

Attacked

Chicken 

0.15

1.0

1.5

2.0

2.0

3.0

Cow

0.25

1.0

1.5

2.0

2.0

3.0

Pig

0.20

1.0

1.5

2.0

2.0

3.0

Sheep

0.20

1.0

1.5

2.0

2.0

3.0

 

 

변경 사항

마우스 커서에 바라보는 동물의 상태가 보여집니다.

포션 효과, 배고픔, 체력, 나이(임신/성장), 조련이 표시됩니다.

 

바닥재(Block)가 추가됩니다.

나무 바닥재는 어린 동물을 더 빠르게 자라게 합니다.
양털 바닥재는 아기를 더 빠르게 낳도록 합니다.
밀짚 바닥재는 배설물을 더 빠르게 분해합니다.

밀짚 바닥재 위에서 배설물 분해속도는 컨피그를 통해 수정할 수 있습니다.

 

대형 크랭크(Block)가 추가됩니다.

3*1*3의 구조입니다.
소형 크랭크보다 많은 동력을 제공합니다.
길들여진 동물을 밧줄(lead)을 이용하여 매어야 동작합니다.
매여진 동물은 배가 불러야만 일을 합니다.

 

디버그 돋보기가 추가됩니다.

우클릭으로 목표 동물을 설정할 수 있습니다.
설정된 동물의 능력치를 정확히 표시합니다.
배고픔과 길들임은 방향키를 이용하여 값을 수정할 수 있습니다.

 

맷돌이 추가됩니다.

씨앗을 갈아 씨앗 기름을 짜는 기계입니다.
씨앗기름은 아직 쓰임새가 없습니다.
조합을 통해 획득할 수 없습니다.
분쇄 조합법은 컨피그에서 수정 가능합니다.

여물통의 조합법이 변경되었습니다.

합성목재가 필요합니다.

 

탈곡기가 추가됩니다.

탈곡기는 밀이나 양귀비에서 열매와 줄기를 분리합니다.
탈곡 조합법은 컨피그에서 수정 가능합니다.

 

분쇄기가 추가됩니다.

분쇄기는 2개 이상의 아이템을 섞어 새로운 아이템으로 만듭니다.
분쇄 조합법은 컨피그에서 수정 가능합니다.

 

합성 목재가 추가됩니다.

합성목재는 서로 다른 종류의 원목과 아교를 조합해서 만들어집니다.

A

B

C

A

B

C

A

B

C

A: wood log (all of A are same, different from C), B: animal glue, C: wood log (all of C are same, different from A)

 

아교가 추가됩니다.

아교는 도가니를 뼈, 힘줄, 가죽 등을 든 상태에서 우클릭하여 만듭니다.

 

축, 바퀴, 벨트가 추가됩니다.

모두 동력 수송을 위해 사용됩니다.

 

양귀비를 재배할 수 있습니다.

경작지에 양귀비를 직접 심으면 됩니다.

 

1.7.10~1.8로 변환하며 생긴 변경 사항

함정 덮개의 겉모습 개선됩니다.
사냥추의 애니메이션이 개선됩니다.
새총의 고무줄 텍스쳐와 애니메이션이 개선됩니다.
양고기도 소고기나 돼지고기와 같이 죽는 동물의 배고픔에 비례하여 떨어집니다.

 

컨피그

World, Recipe, Animal 세 파일로 나뉘어집니다.

'마인크래프트 자작 모드 > 배고픈동물들' 카테고리의 다른 글

2.1 개발 현황  (0) 2015.10.26
1.0.4.1 업데이트  (6) 2015.02.06
1.0.4.0 업데이트  (2) 2015.02.05
1.0.3.1 업데이트  (0) 2015.02.05
1.0.3 업데이트  (1) 2015.02.05

안녕하세요. 약 1주일이 안 되는 시간 동안 새로운 모드를 만들었습니다. 이 모드는 청크 로딩을 관리하기 위해 만든 모드입니다. 플레이어 입장에서는 원하는 청크를 쉽게 로드하고, 관리자는 서버 사양에 맞춰 그 양을 조절할 수 있도록 합니다. 서버 관리자는 다른 모드를 추가적으로 사용한다면 플레이어 입장에서도 흥미롭고 관리도 편한 시스템을 구축할 수 있을 것입니다.

 

마인크래프트 1.7.10, Forge 10.13.2.1291을 사용하여 개발했습니다. 다운로드는 http://adf.ly/11RnsV에서 하실 수 있습니다.

 

 

토지 중개사 모드는 단 2개의 아이템 만을 추가합니다. 토지 문서(Land Document)와 토지 대장(Land Book)입니다. 이 두 아이템을 이용하여 플레이어는 청크 로딩을 관리할 수 있습니다.

 

 

 

토지 문서는 기본적으로 제작법이 없는 아이템입니다. 크리에이티브 모드의 Search Items 탭이나 OP의 명령어를 이용해서만 얻을 수 있습니다. 플레이어가 토지 문서를 어떻게 획득할 지 설정하는 것은 모두 서버 관리자에게 맡기기로 했기 때문입니다. 토지 문서를 우클릭으로 사용하면 해당 청크는 로딩되며 동시에 토지 문서가 소모됩니다. 한 플레이어가 같은 청크에 여러 개의 토지 문서를 사용할 수는 없으나 서로 다른 플레이어가 같은 청크에 사용하는 것은 가능합니다. 그러나 여기서 플레이어가 얻을 수 있는 이익은 없습니다. 청크 로딩은 최대한 겹치지 않게 하세요.

 

 

 

토지 대장은 조합법은 없으나 누구나 명령어로 획득할 수 있습니다. /landbook 또는 /lc 명령어를 이용하면 즉시 토지 대장을 획득합니다. 토지 대장에서 해당 플레이어가 로딩하는 모든 청크를 확인 할 수 있습니다. 또한 청크로부터 토지 문서를 회수하여 로딩을 취소할 수 있습니다. 이때 토지 문서는 되돌려 받습니다.

 

 

 

이 모드는 어떤 모드 팩에도 들어갈 수 있습니다. 다만 재 배포 및 영리적 사용에 관해서는 문의 부탁드립니다.

새로운 엔티티(Entity)의 행동은 크게 2가지를 통해 구현할 수 있습니다. 가장 간단한 방법은 onUpdate() 함수 내에서 엔티티(Entity)의 상태를 분석하여 행동을 유도하는 것입니다. 다른 방법은 이 강의에서 알아보겠지만 AI를 사용하는 것입니다. 바닐라(Vanilla)로 예를 들자면, 닭이 알을 낳는 것은 첫 번째 방법으로 구현되어 있습니다. 하지만 플레이어(Player)를 따라오고, 걸어 다니는 것을 포함한 대부분의 행동은 AI를 이용합니다. 교배의 경우에는 첫 번째 방법과 두 번째 방법 모두 구현되어 있으나 AI를 이용한 코드만이 동작하고 있습니다.

이번 강의에서는 AI의 작동 원리와 구현법을 알아 볼 것입니다. 마인크래프트 모드 제작엔티티(Entity)에 대한 기본적인 내용을 숙지하고 계셔야 강의를 온전히 이해하실 수 있습니다.

 

AI의 작동 원리

 

작동 중인 AI와 대기 중인 AI

엔티티(Entity)는 "tasks"라는 리스트(List)에 AI를 저장합니다. 이렇게 저장된 AI는 크게 2가지로 나눌 수 있습니다. 작동 중인 AI와 작동 중이지 않은(대기 중인) AI. 작동 중인 AI는 continueExecuting()이라는 함수를 통해 계속 작동할 것인지를 결정하고, 작동 중이지 않은 AI는 shouldExecute()라는 함수를 통해 작동을 시작할 것인지를 결정합니다. 또한 작동 중인 AI는 updateTask()라는 함수에서 그 내용을 실행하여 원하는 기능대로 동작합니다.

위의 작동 원리는 AI의 개별적인 정보만을 고려한 것입니다. 그러나 실제 동작에는 하나의 엔티티(Entity)라고 할지라도 여러 AI가 동시에 존재하여 서로 충돌의 가능성이 있습니다. 그렇기 때문에 각 AI에는 우선권(Priority)과 함께 isInterruptible, MutexBit의 개념이 있습니다. 이 세 개념은 continueExecuting(), shoudExecute()가 실행될 때, 함께 고려됩니다.

 

사용 가능성

작동 중인 AI가 계속 동작하고, 대기 중인 AI가 새로 실행되기 위해서는 사용 가능성 검사를 통과 해야 합니다. 사용 가능성이란 해당 AI가 동작 중인 모든 AI에 대해 우선권이 같거나 높은 대상과는 호환가능(Compatible) 해야 하고 낮은 대상들은 모두 중지가능(Interruptible) 한 것을 말합니다. 여기서 호환 가능하다는 것은 두 AI가 동시에 동작 가능하다는 것을 의미하고 중지 가능이라는 것은 AI가 동작 중간에 취소되는 것을 허용한다는 의미입니다.

AI 1~3은 동작 중인 AI입니다. 대상 AI는 대기 중인 AI라고 가정합니다. 그렇다면 대상 AI가 AI 2와 호환 불가능이기 때문에 실행이 되지 않을 것입니다. 여담이지만 사용 가능성 검사는 동작 중인 AI를 대상으로 먼저 시행되는데, AI 1~3은 모두 해당 틱(Tick)에서 사용 가능성 검사를 통과하였으므로(= 동작 중이므로) 서로 호환 가능일 것입니다.

계속 위의 상황에서 만약 대상 AI가 동작 중인 AI라고 가정해봅시다. 결론적으로는 AI 2로 인해 대상 AI는 대기 상태로 되돌아 갈 것입니다. 여기서 조금만 더 분석해 봅시다. 대상 AI는 분명 동작 중이었기 때문에 분명 AI 2보다는 먼저 실행되었을 것입니다. 대상 AI가 AI 2와 호환 불가능 관계이고 대상 AI가 이미 작동 중이었는데도 불구하고 AI 2가 실행되었다는 것은, AI 2의 우선권이 대상 AI보다 높고, 대상 AI는 중지 가능한 AI라는 것을 유추할 수 있어야 합니다. 이처럼 우선권이 높은 AI는 중지 가능한 AI를 강제로 종료시킬 수 있습니다.

 

호환 가능성과 중지 가능성

이제 호환 가능성과 중지 가능성에 대해 조금 더 자세히 알아보겠습니다. 중지 가능성은 각 AI 클래스(Class) 내부의 boolean isInterruptible() 함수의 반환 값으로 결정됩니다. 기본적으로 EntityAIBase에는 이 함수가 true를 반환하도록 설정되어 있습니다. 즉, 특별히 오버라이드(Override)하지 않는다면 중지 가능한 AI로 설정됩니다.

호환 가능성은 보다 복잡한 개념인 MutexBit를 사용합니다. MutexBit는 각 2진수의 자리를 통해 해당 AI의 동작을 간단히 분류하는 것이라 할 수 있습니다. 대부분 이동을 요구하는 AI는 1의 MutexBit를 포함합니다. 시선 처리를 요구하는 AI는 2의 MutexBit를 가집니다. 예를 들어 교배를 담당하는 AI는 이동과 시선 모두 요구하므로 (1+2)의 MutexBit를 가집니다.

이동을 요구하는 두 개의 AI는 동시에 실행 될 수 없을 것입니다. 또한 시선 처리를 요구하는 AI도 두 방향을 동시에 바라 볼 수 없으니 마찬가지 입니다. 따라서 두 AI의 MutexBit의 각 2진수 자리를 비교하여 겹치는 자리가 없으면 호환 가능하다고 할 수 있습니다. 수식으로 분석하면bit1 & bit2 == 0일 때 호환 가능한 것 입니다.

 

 

AI 구현 방법

 

지금까지 AI의 작동원리에 대해서 알아봤습니다. 위의 설명에 간혹 실제 함수 이름이 노출되기도 했지만 그것으로는 구현에 부족할 태니 실제 코드를 보며 AI 구현법에 대해 설명하겠습니다. 위의 작동 원리를 명확하게 이해하지 못한다면 원하는 AI를 완벽하게 구현하는 것은 힘듭니다.

 

EntityAIBase를 상속하는 클래스(Class)

AI는 반드시 EntityAIBase를 상속하여 구현됩니다. EntityAIBase는 추상(Abstract) 클래스(Class)이기 때문에 상속 시 반드시 오버라이드(Override) 해야 하는 함수들이 있습니다. 생성자와 함께 다음과 같이 구현합니다.

 

public class EntityAITest extends EntityAIBase {

 

    public EntityAITest() {

        

    }

    

    @Override

    public boolean shouldExecute() {

        // TODO Auto-generated method stub

        return false;

    }

 

}

 

하지만 위의 함수만 입력한다고 원하는 기능을 구현할 수 있는 것이 아닙니다. 따라서 본문에서 잠시 언급되었던 함수들을 모두 오버라이드(Override) 합니다.

 

public class EntityAITest extends EntityAIBase {

 

    public EntityAITest() {

        this.setMutexBits(1);

        // 만약 엔티티(Entity) 이동과 관련된 함수라면 이처럼 MutexBit 1 포함시켜야 합니다.

    }

    

    @Override

    public boolean isInterruptible() {

        // 중지 가능성을 말합니다.

        return super.isInterruptible();

    }

    

    @Override

    public boolean shouldExecute() {

        // AI 대기 상태일 호출되어 새로 실행되어야 하는 조건을 확인합니다.

        return false;

    }

    

    @Override

    public void startExecuting() {

        // shouldExecute() == true 만족하여 AI 새로 동작할 , 처음 실행되는 함수입니다.

        super.startExecuting();

    }

    

    @Override

    public void updateTask() {

        // AI 동작 상태일 (Tick)마다 호출되는 함수입니다.

        super.updateTask();

    }

    

    @Override

    public boolean continueExecuting() {

        // AI 계속 동작할 것인지를 판정합니다.

        return super.continueExecuting();

    }

 

}

 

대부분의 함수가 언급되었고 주석으로 설명을 달아 놓았으니 확인하시길 바랍니다.

 

엔티티(Entity)에게 AI 추가하기

마지막 단계입니다. 성공적으로 AI가 구현되었다면 이제 대상 엔티티(Entity)에게 인공지능을 주입해야 합니다. 위에서 언급되었던 것처럼 'tasks' 리스트(List)에 추가하게 됩니다. 일반적인 경우 엔티티(Entity)의 생성자에서 진행하는 것이 좋습니다.

 

    public EntityHungryChicken(World world) {

        super(world);

        

        this.tasks.addTask(1, new EntityAITest());

        //addTack(우선권, AI)으로 인자(Parameter) 받습니다. 정수만 가능하며 숫자가 작을수록 높은 우선권을 지닙니다.

    }

    

    public boolean isAIEnabled() {

        // 이것이 true 아니면 새로 생성자에서 추가된 AI 작동하지 않습니다.

        return true;

    }

 

여기서 주목해야 할 곳이 두 군데 있습니다. 첫 번째는 addTask()에서 우선권을 지정하는 수는 낮을수록 우선순위가 높다는 점입니다. 두 번째는 isAIEnabled() 함수가 true를 반환하지 않으면 추가된 AI가 작동하지 않는다는 점입니다. 이 두 사항을 모두 확인하여 구현에 어려움이 없으시길 바랍니다.

 

 

 

이것으로 AI에 대한 강의를 마칩니다. 몇 가지 유의할 사항이 있습니다. 일단 위 강의는 tasks 리스트(List)에 추가되는 종류의 AI에 대한 내용입니다. 예를 들어 공격할 상대를 지정하는 AI는 targetTasks에 저장됩니다. 구체적인 구현 방법은 바닐라(Vanilla) 마인크래프트에서 사용된 예들을 살펴보시길 바랍니다. 관련된 개념이나 함수에 대한 질문은 원하시는 만큼 자세히 답변해 드리겠습니다. 감사합니다.

+ Recent posts