Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code: - Client: Game client source (moved to Client/Client/) - Server: Game server source - GameTools: Development tools - CryptoSource: Encryption utilities - database: Database scripts - Script: Game scripts - rylCoder_16.02.2008_src: Legacy coder tools - GMFont, Game: Additional resources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,676 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
#include <Item/Item.h>
|
||||
#include <Item/ItemMgr.h>
|
||||
#include <Item/ItemFactory.h>
|
||||
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/Character/Character.h>
|
||||
|
||||
#include <GameEvent/GameEventMgr.h>
|
||||
|
||||
#include "Monster.h"
|
||||
#include "AwardTable.h"
|
||||
|
||||
|
||||
using namespace AwardTable;
|
||||
|
||||
CAward CAward::ms_this;
|
||||
|
||||
unsigned long CAward::GetAward(unsigned char cItemKind, CMonster* lpDeadMonster, CCharacter* lpAttackCharacter,
|
||||
Item::CItem** lpItem_Out)
|
||||
{
|
||||
if (NULL == lpDeadMonster || NULL == lpAttackCharacter)
|
||||
{
|
||||
ERRLOG2(g_Log, "CAward::GetAward 포인터 오류입니다. Monster:0x%p, Character:0x%p", lpDeadMonster, lpAttackCharacter);
|
||||
return 0;
|
||||
}
|
||||
// edith 드랍 아이템의 속성별 확율
|
||||
|
||||
// 이벤트 아이템이 드랍될 타이밍인지 체크한다.
|
||||
unsigned long dwResultItem = CGameEventMgr::GetInstance().PopDropEventItem();
|
||||
|
||||
if (0 == dwResultItem)
|
||||
{
|
||||
const unsigned char cLevel = lpDeadMonster->GetStatus().m_nLevel;
|
||||
const unsigned int iPattern = lpDeadMonster->GetPattern();
|
||||
|
||||
using namespace Item::EquipType;
|
||||
|
||||
OptionType eOptionType = MAX_OPTION_TYPE;
|
||||
Grade eGrade = MAX_GRADE;
|
||||
float fAddPer = 1.0f;
|
||||
|
||||
switch (cItemKind)
|
||||
{
|
||||
case MonsterInfo::ORIGINAL1:
|
||||
case MonsterInfo::ORIGINAL2:
|
||||
case MonsterInfo::ORIGINAL3:
|
||||
case MonsterInfo::ORIGINAL4:
|
||||
case MonsterInfo::ORIGINAL5:
|
||||
dwResultItem = lpDeadMonster->GetAwardItem(cItemKind);
|
||||
break;
|
||||
|
||||
case MonsterInfo::COIN:
|
||||
// 골드에 스틸핸드가 미치는 영향
|
||||
if(lpAttackCharacter->GetEnchantInfo().GetFlag(Skill::SpellID::StealHand))
|
||||
{
|
||||
// 배수로 확정한다. 1이면
|
||||
int iLevel = lpAttackCharacter->GetEnchantLevel(Skill::SpellID::StealHand);
|
||||
// 기본 1.0
|
||||
fAddPer += ((float)iLevel/10.0f);
|
||||
}
|
||||
|
||||
if( lpAttackCharacter->GetAbilityValue(Skill::Type::AB_GETUP_GOLD) )
|
||||
{
|
||||
fAddPer += ((float)lpAttackCharacter->GetAbilityValue(Skill::Type::AB_GETUP_GOLD)/100.0f);
|
||||
}
|
||||
|
||||
dwResultItem = GetAwardCoin(cLevel, fAddPer);
|
||||
break;
|
||||
|
||||
case MonsterInfo::GEM: dwResultItem = GetAwardGem(cLevel); break;
|
||||
case MonsterInfo::METAL: dwResultItem = GetAwardMetal(cLevel); break;
|
||||
case MonsterInfo::POTION: dwResultItem = GetAwardPotion(cLevel); break;
|
||||
|
||||
case MonsterInfo::SKILL:
|
||||
dwResultItem = GetAwardSkill(cLevel, lpAttackCharacter->GetRace());
|
||||
break;
|
||||
|
||||
case MonsterInfo::CASH_ITEM: dwResultItem = GetAwardCashItem(lpAttackCharacter->GetRace(), cLevel, iPattern); break;
|
||||
case MonsterInfo::LOTTERY: dwResultItem = GetAwardLottery(); break;
|
||||
|
||||
case MonsterInfo::F_STANDARD_EQUIP: eOptionType = STANDARD_OPTION; eGrade = F_GRADE; break;
|
||||
case MonsterInfo::D_STANDARD_EQUIP: eOptionType = STANDARD_OPTION; eGrade = D_GRADE; break;
|
||||
case MonsterInfo::C_STANDARD_EQUIP: eOptionType = STANDARD_OPTION; eGrade = C_GRADE; break;
|
||||
case MonsterInfo::B_STANDARD_EQUIP: eOptionType = STANDARD_OPTION; eGrade = B_GRADE; break;
|
||||
case MonsterInfo::A_STANDARD_EQUIP: eOptionType = STANDARD_OPTION; eGrade = A_GRADE; break;
|
||||
|
||||
case MonsterInfo::F_OVER_EQUIP: eOptionType = OVER_OPTION; eGrade = F_GRADE; break;
|
||||
case MonsterInfo::D_OVER_EQUIP: eOptionType = OVER_OPTION; eGrade = D_GRADE; break;
|
||||
case MonsterInfo::C_OVER_EQUIP: eOptionType = OVER_OPTION; eGrade = C_GRADE; break;
|
||||
case MonsterInfo::B_OVER_EQUIP: eOptionType = OVER_OPTION; eGrade = B_GRADE; break;
|
||||
case MonsterInfo::A_OVER_EQUIP: eOptionType = OVER_OPTION; eGrade = A_GRADE; break;
|
||||
|
||||
case MonsterInfo::NO_EQUIP: eOptionType = NO_OPTION; break;
|
||||
|
||||
//--// start..
|
||||
case MonsterInfo::F_RING: dwResultItem = GetAwardRing(cLevel, F_GRADE); break;
|
||||
case MonsterInfo::D_RING: dwResultItem = GetAwardRing(cLevel, D_GRADE); break;
|
||||
case MonsterInfo::C_RING: dwResultItem = GetAwardRing(cLevel, C_GRADE); break;
|
||||
case MonsterInfo::B_RING: dwResultItem = GetAwardRing(cLevel, B_GRADE); break;
|
||||
case MonsterInfo::A_RING: dwResultItem = GetAwardRing(cLevel, A_GRADE); break;
|
||||
|
||||
case MonsterInfo::F_NECKLACE: dwResultItem = GetAwardNecklace(cLevel, F_GRADE); break;
|
||||
case MonsterInfo::D_NECKLACE: dwResultItem = GetAwardNecklace(cLevel, D_GRADE); break;
|
||||
case MonsterInfo::C_NECKLACE: dwResultItem = GetAwardNecklace(cLevel, C_GRADE); break;
|
||||
case MonsterInfo::B_NECKLACE: dwResultItem = GetAwardNecklace(cLevel, B_GRADE); break;
|
||||
case MonsterInfo::A_NECKLACE: dwResultItem = GetAwardNecklace(cLevel, A_GRADE); break;
|
||||
|
||||
case MonsterInfo::F_RUNE: dwResultItem = GetAwardRune(cLevel, F_GRADE); break;
|
||||
case MonsterInfo::D_RUNE: dwResultItem = GetAwardRune(cLevel, D_GRADE); break;
|
||||
case MonsterInfo::C_RUNE: dwResultItem = GetAwardRune(cLevel, C_GRADE); break;
|
||||
case MonsterInfo::B_RUNE: dwResultItem = GetAwardRune(cLevel, B_GRADE); break;
|
||||
case MonsterInfo::A_RUNE: dwResultItem = GetAwardRune(cLevel, A_GRADE); break;
|
||||
|
||||
case MonsterInfo::DESTRUCTION_RUNE: dwResultItem = GetAwardDestructionRune(); break;
|
||||
//--// end..
|
||||
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
Item::CItemFactory& ItemFactory = Item::CItemFactory::GetInstance();
|
||||
if (MAX_OPTION_TYPE != eOptionType)
|
||||
{
|
||||
// 장비의 경우
|
||||
dwResultItem = GetAwardEquipment(eOptionType, cLevel, lpAttackCharacter, false);
|
||||
|
||||
*lpItem_Out = ItemFactory.CreateItem(static_cast<unsigned short>(dwResultItem));
|
||||
Item::CEquipment* lpEquip = Item::CEquipment::DowncastToEquipment(*lpItem_Out);
|
||||
|
||||
if (NULL != lpEquip)
|
||||
{
|
||||
// edith 2008.01.14 장비에 옵션을 랜덤으로 건다.(설명)
|
||||
if(lpEquip->AddRandomOption(eGrade, lpDeadMonster->GetOptionBaseNum(eOptionType), lpAttackCharacter->GetMagicChancePoint()))
|
||||
lpEquip->SetNewEquip(2);
|
||||
else
|
||||
lpEquip->SetNewEquip();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(dwResultItem & CCell::MONEY_BIT))
|
||||
{
|
||||
// 돈이 아닌 기타 잡템
|
||||
*lpItem_Out = ItemFactory.CreateItem(static_cast<unsigned short>(dwResultItem));
|
||||
if (NULL != *lpItem_Out)
|
||||
{
|
||||
(*lpItem_Out)->SetNumOrDurability(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dwResultItem;
|
||||
}
|
||||
|
||||
|
||||
unsigned long CAward::GetAwardCoin(unsigned char cDeadMonsterLevel, float cAddPer)
|
||||
{
|
||||
// edith 2009.06.13 골드 드랍양 조절
|
||||
// const unsigned long dwMinMoney = static_cast<unsigned long>(cDeadMonsterLevel) * 2;
|
||||
// const unsigned long dwMaxMoney = static_cast<unsigned long>(cDeadMonsterLevel) * 4 + 10;
|
||||
|
||||
const unsigned long dwMinMoney = static_cast<unsigned long>(cDeadMonsterLevel) * 3 + 6;
|
||||
const unsigned long dwMaxMoney = static_cast<unsigned long>(cDeadMonsterLevel) * 6 + 12;
|
||||
|
||||
unsigned long dwMoneyAmount =
|
||||
static_cast<unsigned long>( min(Math::Random::ComplexRandom(dwMaxMoney + 1, dwMinMoney) * (CServerSetup::GetInstance().GetDropDefault() / 100.0f) * cAddPer,
|
||||
float(CCell::MAX_MONEY_AMOUNT)) );
|
||||
|
||||
dwMoneyAmount = min(dwMoneyAmount, ULONG_MAX & ~CCell::TYPE_CHECK_BIT);
|
||||
|
||||
return (CCell::MONEY_BIT + dwMoneyAmount);
|
||||
}
|
||||
|
||||
|
||||
unsigned long CAward::GetAwardGem(unsigned char cDeadMonsterLevel)
|
||||
{
|
||||
cDeadMonsterLevel = std::min(cDeadMonsterLevel, unsigned char(MAX_MONSTER_LEVEL));
|
||||
|
||||
const int nPoint = Math::Random::ComplexRandom(std::accumulate(&(aryGemDropTable[(cDeadMonsterLevel - 1) / 10][0]),
|
||||
&(aryGemDropTable[(cDeadMonsterLevel - 1) / 10][GEM_DROP_TABLE_ROW]), 0));
|
||||
int nSpace = 0;
|
||||
|
||||
for (int nAwardTableIndex = 0; nAwardTableIndex < GEM_DROP_TABLE_ROW; ++nAwardTableIndex)
|
||||
{
|
||||
nSpace += aryGemDropTable[(cDeadMonsterLevel - 1) / 10][nAwardTableIndex];
|
||||
if (nSpace > nPoint)
|
||||
{
|
||||
return (Item::EtcItemID::GEM_START_ID +
|
||||
Math::Random::ComplexRandom(Item::MAX_GEM_KIND) + (nAwardTableIndex * Item::MAX_GEM_KIND));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//--// start..
|
||||
unsigned long CAward::GetAwardRing(unsigned char cLevel, unsigned char cGrade)
|
||||
{
|
||||
return Item::CItemMgr::GetInstance().GetRingDropItem(cLevel, cGrade);
|
||||
}
|
||||
|
||||
unsigned long CAward::GetAwardNecklace(unsigned char cLevel, unsigned char cGrade)
|
||||
{
|
||||
return Item::CItemMgr::GetInstance().GetNecklaceDropItem(cLevel, cGrade);
|
||||
}
|
||||
|
||||
unsigned long CAward::GetAwardRune(unsigned char cLevel, unsigned char cGrade)
|
||||
{
|
||||
return Item::CItemMgr::GetInstance().GetRuneDropItem(cLevel, cGrade);
|
||||
}
|
||||
|
||||
unsigned long CAward::GetAwardDestructionRune()
|
||||
{
|
||||
return Item::EtcItemID::DESTRUCTION_RUNE;
|
||||
}
|
||||
//--// end..
|
||||
|
||||
unsigned long CAward::GetAwardMetal(unsigned char cDeadMonsterLevel)
|
||||
{
|
||||
cDeadMonsterLevel = std::min(cDeadMonsterLevel, unsigned char(MAX_MONSTER_LEVEL));
|
||||
|
||||
const int nPoint = Math::Random::ComplexRandom(std::accumulate(&(aryMetalDropTable[(cDeadMonsterLevel - 1) / 10][0]),
|
||||
&(aryMetalDropTable[(cDeadMonsterLevel - 1) / 10][METAL_DROP_TABLE_ROW]), 0));
|
||||
int nSpace = 0;
|
||||
|
||||
for (int nAwardTableIndex = 0; nAwardTableIndex < METAL_DROP_TABLE_ROW; ++nAwardTableIndex)
|
||||
{
|
||||
nSpace += aryMetalDropTable[(cDeadMonsterLevel - 1) / 10][nAwardTableIndex];
|
||||
if (nSpace > nPoint)
|
||||
{
|
||||
// 코어시스템
|
||||
if(nAwardTableIndex == 6)
|
||||
{
|
||||
// edith 2009.09.17 코어시스템
|
||||
// 현재로서는 각성의 코어만 있으므로 각성의 코어를 떨군다.
|
||||
return Item::EtcItemID::AWAKENCORE;
|
||||
}
|
||||
else
|
||||
{
|
||||
int iMetalID = (Item::EtcItemID::MINERAL_START_ID + nAwardTableIndex);
|
||||
|
||||
if(iMetalID > Item::EtcItemID::MINERAL_END_ID)
|
||||
iMetalID = Item::EtcItemID::MINERAL_END_ID;
|
||||
|
||||
return iMetalID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long CAward::GetAwardPotion(unsigned char cDeadMonsterLevel)
|
||||
{
|
||||
cDeadMonsterLevel = std::min(cDeadMonsterLevel, unsigned char(MAX_MONSTER_LEVEL));
|
||||
|
||||
const int nPoint = Math::Random::ComplexRandom(std::accumulate(&(aryPotionDropTable[(cDeadMonsterLevel - 1) / 10][0]),
|
||||
&(aryPotionDropTable[(cDeadMonsterLevel - 1) / 10][POTION_DROP_TABLE_ROW]), 0));
|
||||
|
||||
int nSpace = 0;
|
||||
|
||||
for (int nAwardTableIndex = 0; nAwardTableIndex < POTION_DROP_TABLE_ROW; ++nAwardTableIndex)
|
||||
{
|
||||
nSpace += aryPotionDropTable[(cDeadMonsterLevel - 1) / 10][nAwardTableIndex];
|
||||
if (nSpace > nPoint)
|
||||
{
|
||||
if(nAwardTableIndex <= 3)
|
||||
{
|
||||
const int iType = Math::Random::ComplexRandom(2);
|
||||
// 빵들.
|
||||
if(iType == 0)
|
||||
{
|
||||
switch (nAwardTableIndex)
|
||||
{
|
||||
case 0: return Item::EtcItemID::BREAD;
|
||||
case 1: return Item::EtcItemID::RARE_STEAK;
|
||||
case 2: return Item::EtcItemID::WELLDONE_STEAK;
|
||||
case 3: return Item::EtcItemID::BACON;
|
||||
}
|
||||
}
|
||||
else if(iType == 1)
|
||||
{
|
||||
switch (nAwardTableIndex)
|
||||
{
|
||||
case 0: return Item::EtcItemID::MANNA1;
|
||||
case 1: return Item::EtcItemID::MANNA2;
|
||||
case 2: return Item::EtcItemID::MANNA3;
|
||||
case 3: return Item::EtcItemID::MANNA4;
|
||||
}
|
||||
}
|
||||
else if(iType == 2)
|
||||
{
|
||||
switch (nAwardTableIndex)
|
||||
{
|
||||
case 0: return Item::EtcItemID::SOUP1;
|
||||
case 1: return Item::EtcItemID::SOUP2;
|
||||
case 2: return Item::EtcItemID::SOUP3;
|
||||
case 3: return Item::EtcItemID::SOUP4;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// 4부터 포션 0랩 포션
|
||||
// 포션계열,
|
||||
int iLevel = nAwardTableIndex-4;
|
||||
const int iType = Math::Random::ComplexRandom(14);
|
||||
switch(iType)
|
||||
{
|
||||
case 0: return Item::EtcItemID::POTION1+iLevel;
|
||||
case 1: return Item::EtcItemID::POTION2+iLevel;
|
||||
case 2: return Item::EtcItemID::POTION3+iLevel;
|
||||
case 3: return Item::EtcItemID::POTION4+iLevel;
|
||||
case 4: return Item::EtcItemID::POTION5+iLevel;
|
||||
case 5: return Item::EtcItemID::POTION6+iLevel;
|
||||
case 6: return Item::EtcItemID::POTION7+iLevel;
|
||||
case 7: return Item::EtcItemID::POTION8+iLevel;
|
||||
case 8: return Item::EtcItemID::POTION9+iLevel;
|
||||
case 9: return Item::EtcItemID::POTION10+iLevel;
|
||||
case 10: return Item::EtcItemID::POTION11+iLevel;
|
||||
case 11: return Item::EtcItemID::POTION12+iLevel;
|
||||
case 12: return Item::EtcItemID::POTION13+iLevel;
|
||||
case 13: return Item::EtcItemID::POTION14+iLevel;
|
||||
// case 14: return Item::EtcItemID::POTION15+iLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
unsigned long CAward::GetAwardSkill(unsigned char cDeadMonsterLevel, char cAttackCharacterNation)
|
||||
{
|
||||
cDeadMonsterLevel = std::min(cDeadMonsterLevel, unsigned char(MAX_MONSTER_LEVEL));
|
||||
const short nDeadMonsterLevelRange = (cDeadMonsterLevel - 1) / 10;
|
||||
|
||||
const short nPoint = static_cast<short>(Math::Random::ComplexRandom(arySkillBookDropTable[nDeadMonsterLevelRange] * 3));
|
||||
|
||||
short nSkillLevel = (101 <= cDeadMonsterLevel) ? 4 : nPoint % arySkillBookDropTable[nDeadMonsterLevelRange];
|
||||
|
||||
// edith 2008.05.07 스킬북 드랍 설정
|
||||
if (true == CServerSetup::GetInstance().UseContents(GameRYL::LEVEL_LIMIT_90))
|
||||
{
|
||||
// 4단계까지 드랍 가능
|
||||
nSkillLevel = min(nSkillLevel, short(3));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3단계까지 드랍 가능
|
||||
nSkillLevel = min(nSkillLevel, short(2));
|
||||
}
|
||||
/*
|
||||
else if (true == CServerSetup::GetInstance().UseContents(GameRYL::LEVEL_LIMIT_80))
|
||||
{
|
||||
// 4단계까지 드랍 가능
|
||||
nSkillLevel = min(nSkillLevel, short(3));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3단계까지 드랍 가능
|
||||
nSkillLevel = min(nSkillLevel, short(2));
|
||||
}
|
||||
*/
|
||||
const CClass::JobLevel nSkillType = (nPoint < arySkillBookDropTable[nDeadMonsterLevelRange] || 4 <= nSkillLevel) ?
|
||||
CClass::JOB_CHANGE_1ST : CClass::DEFAULT_CLASS;
|
||||
|
||||
int nSkillKind = (4 <= nSkillLevel) ? FIFTHSKILL_KIND : SKILL_KIND;
|
||||
unsigned short* arySkillShuffleList = new unsigned short[nSkillKind];
|
||||
|
||||
for(int i = 0; i < nSkillKind; i++)
|
||||
{
|
||||
arySkillShuffleList[i] = 0;
|
||||
}
|
||||
|
||||
if(4 <= nSkillLevel)
|
||||
{
|
||||
std::copy(aryDropableFifthSkillbookList, aryDropableFifthSkillbookList + nSkillKind, arySkillShuffleList);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::copy(aryDropableSkillbookList, aryDropableSkillbookList + nSkillKind, arySkillShuffleList);
|
||||
}
|
||||
|
||||
std::random_shuffle(arySkillShuffleList, arySkillShuffleList + nSkillKind);
|
||||
|
||||
for (int nAwardTableIndex = 0; nAwardTableIndex < nSkillKind; nAwardTableIndex++)
|
||||
{
|
||||
const unsigned char cSkillBookClass = (arySkillShuffleList[nAwardTableIndex] & ~Skill::SKILL_MASK) >> 8;
|
||||
if ((cSkillBookClass >> 4) == cAttackCharacterNation && CClass::GetJobLevel(cSkillBookClass) == nSkillType)
|
||||
{
|
||||
return Item::CItemMgr::GetInstance().GetItemIDFromSkillID(arySkillShuffleList[nAwardTableIndex], nSkillLevel);
|
||||
}
|
||||
}
|
||||
|
||||
delete arySkillShuffleList;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
unsigned short CAward::GetAwardEquipment(Item::EquipType::OptionType eOptionType,
|
||||
unsigned char cDeadMonsterLevel, CCharacter* lpCharacter, bool bQuestAward)
|
||||
{
|
||||
unsigned short wItemID = 0;
|
||||
|
||||
// 솔로잉
|
||||
if (NULL == lpCharacter->GetParty() || true == bQuestAward)
|
||||
{
|
||||
// edith 2008.01.14 40프로 확율로 클래스에 알맞는 장비 드랍
|
||||
// 40%의 확률로 클래스에 알맞은 장비를 드랍한다.
|
||||
if (Math::Random::ComplexRandom(100) < (unsigned long)(40+lpCharacter->GetAbilityValue(Skill::Type::AB_MYEQUIP_UP)))
|
||||
{
|
||||
unsigned char aryShuffleList[PRIMARY_EQUIP_TABLE_ROW] = { 0, };
|
||||
|
||||
std::copy(aryPrimaryEquipList[lpCharacter->GetClass()],
|
||||
aryPrimaryEquipList[lpCharacter->GetClass()] + PRIMARY_EQUIP_TABLE_ROW,
|
||||
aryShuffleList);
|
||||
|
||||
std::random_shuffle(aryShuffleList, aryShuffleList + PRIMARY_EQUIP_TABLE_ROW);
|
||||
|
||||
Item::CItemType& itemType = Item::CItemType::GetInstance();
|
||||
for (unsigned char cIndex = 0; cIndex < PRIMARY_EQUIP_TABLE_ROW; ++cIndex)
|
||||
{
|
||||
Item::ItemType::Type eItemType = static_cast<Item::ItemType::Type>(aryShuffleList[cIndex]);
|
||||
|
||||
// edith 2008.01.14 옵션타입 알아오기 (스탠다드옵션, 오버옵션, 논옵션)
|
||||
if (MAX_ITEM_TYPE != aryShuffleList[cIndex] &&
|
||||
true == itemType.IsCorrectOptionType(eOptionType, eItemType))
|
||||
{
|
||||
wItemID = Item::CItemMgr::GetInstance().GetDropItem(eItemType, cDeadMonsterLevel);
|
||||
if (0 == wItemID) { continue; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CClass::HUMAN == lpCharacter->GetRace())
|
||||
{
|
||||
wItemID = SelectItemByRace(eOptionType, aryHumanEquipTypeList, HUMAN_EQUIP_TYPE_NUM, cDeadMonsterLevel);
|
||||
}
|
||||
else if (CClass::AKHAN == lpCharacter->GetRace())
|
||||
{
|
||||
wItemID = SelectItemByRace(eOptionType, aryAkhanEquipTypeList, AKHAN_EQUIP_TYPE_NUM, cDeadMonsterLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:0x%08x 캐릭터의 종족이 이상합니다. 종족:%d",
|
||||
lpCharacter->GetCID(), lpCharacter->GetRace());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 파티 플레이
|
||||
else
|
||||
{
|
||||
CCharacterParty* lpParty = reinterpret_cast<CCharacterParty *>(lpCharacter->GetParty());
|
||||
|
||||
CCharacter* aryNearCharacterList[PARTY::MAX_MEM];
|
||||
unsigned char cHighestLevel = 0;
|
||||
|
||||
const unsigned char cNearPartyMemberNum = lpParty->GetNearMemberList(lpCharacter->GetCellPos().m_lpCell,
|
||||
true, aryNearCharacterList, cHighestLevel);
|
||||
if (0 == cNearPartyMemberNum) { return 0; }
|
||||
|
||||
unsigned char cHumanNum = 0;
|
||||
for (unsigned char cMemberIndex = 0; cMemberIndex < cNearPartyMemberNum; ++cMemberIndex)
|
||||
{
|
||||
if (CClass::HUMAN == aryNearCharacterList[cMemberIndex]->GetRace())
|
||||
{
|
||||
++cHumanNum;
|
||||
}
|
||||
}
|
||||
|
||||
// 파티원의 종족 비율에 의해 드랍될 장비 타입의 확률이 결정된다.
|
||||
if (static_cast<unsigned long>(cHumanNum * 100.0f / cNearPartyMemberNum) > Math::Random::ComplexRandom(100))
|
||||
{
|
||||
wItemID = SelectItemByRace(eOptionType, aryHumanEquipTypeList, HUMAN_EQUIP_TYPE_NUM, cDeadMonsterLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
wItemID = SelectItemByRace(eOptionType, aryAkhanEquipTypeList, AKHAN_EQUIP_TYPE_NUM, cDeadMonsterLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == wItemID)
|
||||
{
|
||||
ERRLOG0(g_Log, "드랍할 장비를 결정하지 못하였습니다.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return wItemID;
|
||||
}
|
||||
|
||||
unsigned short CAward::GetQuestEquipmentClass(Item::EquipType::OptionType eOptionType, unsigned char cClass,
|
||||
unsigned char cDeadMonsterLevel, CCharacter* lpCharacter)
|
||||
{
|
||||
unsigned short wItemID = 0;
|
||||
|
||||
unsigned char aryShuffleList[PRIMARY_EQUIP_TABLE_ROW] = { 0, };
|
||||
|
||||
std::copy(aryPrimaryEquipList[cClass],
|
||||
aryPrimaryEquipList[cClass] + PRIMARY_EQUIP_TABLE_ROW,
|
||||
aryShuffleList);
|
||||
|
||||
std::random_shuffle(aryShuffleList, aryShuffleList + PRIMARY_EQUIP_TABLE_ROW);
|
||||
|
||||
Item::CItemType& itemType = Item::CItemType::GetInstance();
|
||||
for (unsigned char cIndex = 0; cIndex < PRIMARY_EQUIP_TABLE_ROW; ++cIndex)
|
||||
{
|
||||
Item::ItemType::Type eItemType = static_cast<Item::ItemType::Type>(aryShuffleList[cIndex]);
|
||||
|
||||
// edith 2008.01.14 옵션타입 알아오기 (스탠다드옵션, 오버옵션, 논옵션)
|
||||
if (MAX_ITEM_TYPE != aryShuffleList[cIndex] &&
|
||||
true == itemType.IsCorrectOptionType(eOptionType, eItemType))
|
||||
{
|
||||
wItemID = Item::CItemMgr::GetInstance().GetDropItem(eItemType, cDeadMonsterLevel);
|
||||
if (0 == wItemID) { continue; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == wItemID)
|
||||
{
|
||||
ERRLOG0(g_Log, "드랍할 장비를 결정하지 못하였습니다.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return wItemID;
|
||||
}
|
||||
|
||||
unsigned long CAward::GetAwardCashItem(char cAttackCharacterNation, unsigned char cDeadMonsterLevel, unsigned int cDeadMonsterPattern)
|
||||
{
|
||||
cDeadMonsterLevel = std::min(cDeadMonsterLevel, unsigned char(MAX_MONSTER_LEVEL));
|
||||
|
||||
int iMonLevel = (cDeadMonsterLevel - 1) / 10;
|
||||
|
||||
if(cDeadMonsterPattern == MonsterInfo::PATTERN_CHIEF) // 치프면 11
|
||||
iMonLevel = 11;
|
||||
else if(cDeadMonsterPattern == MonsterInfo::PATTERN_NAMED) // 네임드면 12 테이블
|
||||
iMonLevel = 12;
|
||||
|
||||
const int nPoint = Math::Random::ComplexRandom(std::accumulate(&(aryCashDropTable[iMonLevel][0]),
|
||||
&(aryCashDropTable[iMonLevel][CASH_DROP_TABLE_ROW]), 0));
|
||||
|
||||
// 캐쉬템의 드랍아이템 리스트는 여기에
|
||||
|
||||
int nSpace = 0;
|
||||
for (int nAwardTableIndex = 0; nAwardTableIndex < CASH_DROP_TABLE_ROW; ++nAwardTableIndex)
|
||||
{
|
||||
nSpace += aryCashDropTable[(cDeadMonsterLevel - 1) / 10][nAwardTableIndex];
|
||||
if (nSpace > nPoint)
|
||||
{
|
||||
if(nAwardTableIndex == 0) // 망각의돌
|
||||
return Item::EtcItemID::OBLIVION_STONE;
|
||||
else if(nAwardTableIndex == 4) // 내구도석
|
||||
return Item::EtcItemID::ENDURANCESTONE;
|
||||
else if(nAwardTableIndex == 5) // 부활의돌
|
||||
return Item::EtcItemID::REBIRTH_STONE;
|
||||
else if(nAwardTableIndex == 6) // 탈것
|
||||
{
|
||||
const int iType = Math::Random::ComplexRandom(2);
|
||||
if(cAttackCharacterNation == 0) //휴먼
|
||||
{
|
||||
if(iType == 0)
|
||||
return Item::EtcItemID::RIDEH1;
|
||||
else
|
||||
return Item::EtcItemID::RIDEH2;
|
||||
}
|
||||
else // 아칸
|
||||
{
|
||||
if(iType == 0)
|
||||
return Item::EtcItemID::RIDEA1;
|
||||
else
|
||||
return Item::EtcItemID::RIDEA2;
|
||||
}
|
||||
}
|
||||
else if(nAwardTableIndex == 3) // 오브
|
||||
{
|
||||
const int iType = Math::Random::ComplexRandom(2);
|
||||
if(iType == 0)
|
||||
return Item::EtcItemID::ORB_EXP;
|
||||
else
|
||||
return Item::EtcItemID::ORB_LUCK;
|
||||
}
|
||||
else if(nAwardTableIndex == 1 || nAwardTableIndex == 2) // 유료 포션
|
||||
{
|
||||
int iLevel = nAwardTableIndex-1;
|
||||
|
||||
int iType = 0;
|
||||
const int iRage = Math::Random::ComplexRandom(100);
|
||||
|
||||
if(iRage < 60)
|
||||
{
|
||||
iType = Math::Random::ComplexRandom(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
iType = 2+Math::Random::ComplexRandom(12);
|
||||
}
|
||||
|
||||
switch(iType)
|
||||
{
|
||||
case 0: return Item::EtcItemID::CASHPOTION1+iLevel;
|
||||
case 1: return Item::EtcItemID::CASHPOTION2+iLevel;
|
||||
case 2: return Item::EtcItemID::CASHPOTION3+iLevel;
|
||||
case 3: return Item::EtcItemID::CASHPOTION4+iLevel;
|
||||
case 4: return Item::EtcItemID::CASHPOTION5+iLevel;
|
||||
case 5: return Item::EtcItemID::CASHPOTION6+iLevel;
|
||||
case 6: return Item::EtcItemID::CASHPOTION7+iLevel;
|
||||
case 7: return Item::EtcItemID::CASHPOTION8+iLevel;
|
||||
case 8: return Item::EtcItemID::CASHPOTION9+iLevel;
|
||||
case 9: return Item::EtcItemID::CASHPOTION10+iLevel;
|
||||
case 10: return Item::EtcItemID::CASHPOTION11+iLevel;
|
||||
case 11: return Item::EtcItemID::CASHPOTION12+iLevel;
|
||||
case 12: return Item::EtcItemID::CASHPOTION13+iLevel;
|
||||
case 13: return Item::EtcItemID::CASHPOTION14+iLevel;
|
||||
// case 14: return Item::EtcItemID::CASHPOTION15+iLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ERRLOG0(g_Log, "드랍할 캐쉬아이템을 결정하지 못하였습니다.");
|
||||
return 0;//Item::EtcItemID::OBLIVION_STONE;
|
||||
}
|
||||
|
||||
|
||||
unsigned long CAward::GetAwardLottery(void)
|
||||
{
|
||||
if (false == CServerSetup::GetInstance().GetLotteryEvent())
|
||||
{
|
||||
ERRLOG0(g_Log, "복권 이벤트를 하고 있지 않은데, 복권 드랍을 요청하였습니다.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CGameEventMgr::GetInstance().GetLotteryEvent().GetLottery();
|
||||
}
|
||||
|
||||
|
||||
unsigned short CAward::SelectItemByRace(Item::EquipType::OptionType eOptionType,
|
||||
const unsigned char* aryItemType, unsigned char cMaxItemType, unsigned char cLevel)
|
||||
{
|
||||
unsigned char aryShuffleList[UCHAR_MAX] = { 0, };
|
||||
|
||||
std::copy(aryItemType, aryItemType + cMaxItemType, aryShuffleList);
|
||||
std::random_shuffle(aryShuffleList, aryShuffleList + cMaxItemType);
|
||||
|
||||
unsigned short wItemID = 0;
|
||||
|
||||
for (unsigned char cIndex = 0; cIndex < cMaxItemType; ++cIndex)
|
||||
{
|
||||
Item::ItemType::Type eItemType = static_cast<Item::ItemType::Type>(aryShuffleList[cIndex]);
|
||||
if (true == Item::CItemType::GetInstance().IsCorrectOptionType(eOptionType, eItemType))
|
||||
{
|
||||
wItemID = Item::CItemMgr::GetInstance().GetDropItem(eItemType, cLevel);
|
||||
if (0 != wItemID)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wItemID;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef _AWARD_TABLE_H_
|
||||
#define _AWARD_TABLE_H_
|
||||
|
||||
#include <Item/ItemConstants.h>
|
||||
|
||||
#include <Creature/Character/CharacterClass.h>
|
||||
#include <Creature/AggresiveCreature.h>
|
||||
|
||||
class CMonster;
|
||||
class CCharacter;
|
||||
|
||||
namespace AwardTable
|
||||
{
|
||||
enum Const
|
||||
{
|
||||
MAX_DROP_ITEM = 5, // 한 몬스터가 최대로 떨구는 아이템 수
|
||||
|
||||
MAX_MONSTER_LEVEL = 109, // 몬스터의 최고 레벨
|
||||
// (보석, 광물, 물약, 스킬북 드랍 테이블용으로 실제와는 다르다. Creature::MONSTER_MAX_LEVEL 참고)
|
||||
|
||||
SKILL_KIND = 88, // 스킬(북)의 종류 (스킬북이 없는 스킬도 있다.)
|
||||
FIFTHSKILL_KIND = 15, // 스킬(북)의 종류 (스킬북이 없는 스킬도 있다.)
|
||||
|
||||
GEM_DROP_TABLE_COLUMN = 11, // 보석 드랍 테이블 세로 크기
|
||||
GEM_DROP_TABLE_ROW = 3, // 보석 드랍 테이블 가로 크기
|
||||
METAL_DROP_TABLE_COLUMN = 11, // 광물 드랍 테이블 세로 크기
|
||||
METAL_DROP_TABLE_ROW = 7, // 광물 드랍 테이블 가로 크기
|
||||
POTION_DROP_TABLE_COLUMN = 11, // 물약 드랍 테이블 세로 크기
|
||||
POTION_DROP_TABLE_ROW = 8, // 물약 드랍 테이블 가로 크기
|
||||
|
||||
CASH_DROP_TABLE_COLUMN = 13, // 캐쉬 드랍 테이블 세로 크기
|
||||
CASH_DROP_TABLE_ROW = 7, // 캐쉬 드랍 테이블 가로 크기
|
||||
|
||||
SKILLBOOK_DROP_TABLE_COLUMN = 11, // 스킬북 드랍 테이블 세로 크기
|
||||
|
||||
EQUIP_MAX_NUM_PER_LEVEL = 15, // 레벨당 드랍되는 장비의 종류수
|
||||
|
||||
BLACK_MARKET_TABLE_COLUMN = 228, // 암시장 테이블 세로 크기
|
||||
BLACK_MARKET_TABLE_ROW = 13, // 암시장 테이블 가로 크기
|
||||
|
||||
PRIMARY_EQUIP_TABLE_ROW = 9, // 장비 드랍 우선 순위 테이블 가로 크기
|
||||
HUMAN_EQUIP_TYPE_NUM = 19, // 휴먼 장비 타입 갯수
|
||||
AKHAN_EQUIP_TYPE_NUM = 19 // 아칸 장비 타입 갯수
|
||||
};
|
||||
|
||||
|
||||
// 아이템 드랍률
|
||||
// Desc : 각 값은 n/1000 의 확률을 의미한다.
|
||||
const unsigned short aryItemDropRate[CAggresiveCreature::AGGRAVATION_NUM][MAX_DROP_ITEM] = {
|
||||
{ 1, 1, 1, 1, 1 }, // -20
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 1, 1, 1, 1, 1 },
|
||||
{ 91, 1, 1, 1, 1 },
|
||||
{ 189, 1, 1, 1, 1 },
|
||||
{ 280, 1, 1, 1, 1 },
|
||||
{ 364, 1, 1, 1, 1 },
|
||||
{ 441, 1, 1, 1, 1 }, // -5
|
||||
{ 511, 1, 1, 1, 1 },
|
||||
{ 574, 1, 1, 1, 1 },
|
||||
{ 623, 1, 1, 1, 1 },
|
||||
{ 665, 91, 1, 1, 1 }, // -1
|
||||
{ 700, 189, 1, 1, 1 }, // 0
|
||||
{ 710, 280, 1, 1, 1 }, // +1
|
||||
{ 720, 364, 1, 1, 1 },
|
||||
{ 730, 441, 1, 1, 1 },
|
||||
{ 740, 511, 1, 1, 1 },
|
||||
{ 750, 574, 1, 1, 1 }, // +5
|
||||
{ 760, 623, 91, 1, 1 },
|
||||
{ 800, 665, 189, 1, 1 },
|
||||
{ 800, 700, 280, 1, 1 },
|
||||
{ 800, 710, 364, 91, 1 }, // +9
|
||||
{ 800, 720, 441, 189, 1 },
|
||||
{ 800, 730, 511, 280, 1 },
|
||||
{ 800, 740, 574, 364, 91 }, // +12
|
||||
{ 900, 750, 623, 441, 189 },
|
||||
{ 900, 760, 665, 511, 280 },
|
||||
{ 900, 800, 700, 574, 364 }, // +15
|
||||
{ 900, 800, 710, 623, 441 }, // +16
|
||||
{ 900, 800, 720, 665, 511 },
|
||||
{ 900, 800, 730, 700, 574 },
|
||||
{ 900, 800, 740, 710, 623 },
|
||||
{ 900, 900, 750, 720, 665 } // +20
|
||||
};
|
||||
/*
|
||||
const unsigned short aryItemDropRate[CAggresiveCreature::AGGRAVATION_NUM][MAX_DROP_ITEM] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 161, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 195, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 228, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 262, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 296, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 392, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 363, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 396, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 430, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 464, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 497, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 531, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 564, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 598, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 632, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 665, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 699, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 732, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 766, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 800, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 51, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 252, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 504, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 807, 1, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 900, 260, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 900, 663, 1, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 900, 900, 216, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 900, 900, 720, 1, 1, 1, 1, 1, 1 },
|
||||
{ 900, 900, 900, 900, 375, 1, 1, 1, 1, 1 },
|
||||
{ 900, 900, 900, 900, 900, 80, 1, 1, 1, 1 },
|
||||
{ 900, 900, 900, 900, 900, 735, 1, 1, 1, 1 },
|
||||
{ 900, 900, 900, 900, 900, 900, 540, 1, 1, 1 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 396, 1, 1 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 303, 1 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 900, 260 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 900, 900 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 900, 900 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 900, 900 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 900, 900 },
|
||||
{ 900, 900, 900, 900, 900, 900, 900, 900, 900, 900 }
|
||||
};
|
||||
*/
|
||||
|
||||
const short aryGemDropTable[GEM_DROP_TABLE_COLUMN][GEM_DROP_TABLE_ROW] = {
|
||||
// 보석조각 작은보석 일반보석
|
||||
{ 100, 0, 0 }, // 10
|
||||
{ 90, 10, 0 }, // 20
|
||||
{ 70, 20, 10 }, // 30
|
||||
{ 50, 30, 20 }, // 40
|
||||
{ 30, 40, 30 }, // 50
|
||||
{ 10, 50, 40 }, // 60
|
||||
{ 10, 30, 60 }, // 70
|
||||
{ 0, 20, 80 }, // 80
|
||||
{ 0, 10, 90 }, // 90
|
||||
{ 0, 10, 90 }, // 100
|
||||
{ 0, 0, 100 } // 110
|
||||
};
|
||||
|
||||
const short aryMetalDropTable[METAL_DROP_TABLE_COLUMN][METAL_DROP_TABLE_ROW] = {
|
||||
// 실빈 미스릴 이터니움 소울메탈 라타리움 고대의돌 코어
|
||||
{ 90, 0, 0, 0, 0, 0, 10 }, // 10
|
||||
{ 81, 9, 0, 0, 0, 0, 10 }, // 20
|
||||
{ 63, 18, 9, 0, 0, 0, 10 }, // 30
|
||||
{ 45, 27, 18, 0, 0, 0, 10 }, // 40
|
||||
{ 27, 36, 27, 0, 0, 0, 10 }, // 50
|
||||
{ 9, 45, 36, 0, 0, 0, 10 }, // 60
|
||||
{ 9, 27, 54, 0, 0, 0, 10 }, // 70
|
||||
{ 0, 18, 72, 0, 0, 0, 10 }, // 80
|
||||
{ 0, 9, 81, 0, 0, 0, 10 }, // 90
|
||||
{ 0, 9, 81, 0, 0, 0, 10 }, // 100
|
||||
{ 0, 0, 90, 0, 0, 0, 10 } // 110
|
||||
};
|
||||
|
||||
const short aryPotionDropTable[POTION_DROP_TABLE_COLUMN][POTION_DROP_TABLE_ROW] = {
|
||||
// 빵 생고기 구운고기 베이컨 버프물약1 버프물약2 버프물약3 버프물약4
|
||||
// 만나1 만나2 만나3 만나4 레벨 레벨 레벨 레벨
|
||||
// 스프1 스프2 스프3 스프4
|
||||
{ 70, 0, 0, 0, 16, 8, 4, 2 }, // 10
|
||||
{ 35, 35, 0, 0, 16, 8, 4, 2 }, // 20
|
||||
{ 23, 23, 24, 0, 16, 8, 4, 2 }, // 30
|
||||
{ 17, 17, 18, 18, 16, 8, 4, 2 }, // 40
|
||||
{ 0, 23, 23, 24, 16, 8, 4, 2 }, // 50
|
||||
{ 0, 23, 35, 24, 16, 8, 4, 2 }, // 60
|
||||
{ 0, 0, 35, 35, 16, 8, 4, 2 }, // 70
|
||||
{ 0, 0, 1, 70, 16, 8, 4, 2 }, // 80
|
||||
{ 0, 0, 0, 70, 16, 8, 4, 2 }, // 90
|
||||
{ 0, 0, 0, 70, 16, 8, 4, 2 }, // 100
|
||||
{ 0, 0, 0, 70, 16, 8, 4, 2 } // 110
|
||||
};
|
||||
|
||||
|
||||
const short aryCashDropTable[CASH_DROP_TABLE_COLUMN][CASH_DROP_TABLE_ROW] = {
|
||||
// 망각의돌 Holy Almighty 오브 내구도석 부활의돌 마운트
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 10
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 20
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 30
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 40
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 50
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 60
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 70
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 80
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 90
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 100
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 110
|
||||
{ 20, 60, 10, 10, 0, 0, 0 }, // 치프
|
||||
{ 0, 20, 50, 16, 8, 4, 2 }, // 네임드
|
||||
};
|
||||
|
||||
|
||||
const short arySkillBookDropTable[SKILLBOOK_DROP_TABLE_COLUMN] = {
|
||||
1, 1, 2, 2, 2,
|
||||
3, 3, 3, 4, 4,
|
||||
5
|
||||
};
|
||||
|
||||
const unsigned short aryDropableSkillbookList[SKILL_KIND] = {
|
||||
// 인간 공통 스킬 17
|
||||
0x8104, 0x8102, 0x8103, 0x8105, 0x8203, 0x8205, 0x8302, 0x8303, 0x8402, 0x8405,
|
||||
0x8106, 0x8107, 0x8202, 0x8304, 0x8305, 0x8306, 0x8404,
|
||||
|
||||
// 인간 상위 스킬 29
|
||||
0x8503, 0x8504, 0x8602, 0x8603, 0x8604, 0x8702, 0x8704, 0x8705, 0x8706, 0x8803,
|
||||
0x8804, 0x8805, 0x8902, 0x8903, 0x8904, 0x8905, 0x8A02, 0x8A04, 0x8A05, 0x8A06,
|
||||
0x8B02, 0x8B03, 0x8B04, 0x8B05, 0x8B06, 0x8C02, 0x8C03, 0x8C04, 0x8C08,
|
||||
|
||||
// 아칸 공통 스킬 17
|
||||
0x9102, 0x9105, 0x9104, 0x9202, 0x9106, 0x9206, 0x9207, 0x9213, 0x9209, 0x9108,
|
||||
0x9109, 0x9110, 0x9204, 0x9208, 0x9210, 0x9212, 0x9214,
|
||||
|
||||
// 아칸 상위 스킬 25
|
||||
0x9302, 0x9305, 0x9307, 0x9402, 0x9404, 0x9405, 0x9407, 0x9502, 0x9504, 0x9506,
|
||||
0x9602, 0x9603, 0x9604, 0x9605, 0x9606, 0x9703, 0x9704, 0x9706, 0x9802, 0x9803,
|
||||
0x9804, 0x9805, 0x9303, 0x9708, 0x9709
|
||||
};
|
||||
|
||||
const unsigned short aryDropableFifthSkillbookList[FIFTHSKILL_KIND] = {
|
||||
// 인간 5단계 스킬
|
||||
0x8603, 0x8503, 0x8704, 0x8804, 0x8902, 0x8a05, 0x8c02, 0x8b04,
|
||||
|
||||
// 아칸 5단계 스킬
|
||||
0x9402, 0x9302, 0x9502, 0x9606, 0x9603, 0x9706, 0x9804
|
||||
};
|
||||
|
||||
using namespace Item::ItemType;
|
||||
const unsigned char aryPrimaryEquipList[CClass::MAX_CLASS][PRIMARY_EQUIP_TABLE_ROW] = {
|
||||
{ MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE },
|
||||
|
||||
{ CON_HELM, CON_GLOVE, CON_BOOTS, CON_ARMOUR, ONEHANDED_SWORD, TWOHANDED_SWORD, ONEHANDED_AXE, TWOHANDED_AXE, SHIELD }, // 파이터
|
||||
{ DEX_HELM, DEX_GLOVE, DEX_BOOTS, DEX_ARMOUR, DAGGER, BOW, CROSSBOW, SHIELD, MAX_ITEM_TYPE }, // 로그
|
||||
{ DEX_HELM, DEX_GLOVE, DEX_BOOTS, DEX_ARMOUR, STAFF, DAGGER, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 메이지
|
||||
{ CON_HELM, CON_GLOVE, CON_BOOTS, CON_ARMOUR, ONEHANDED_BLUNT, TWOHANDED_BLUNT, SHIELD, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 어콜라이트
|
||||
{ CON_HELM, CON_GLOVE, CON_BOOTS, CON_ARMOUR, ONEHANDED_BLUNT, TWOHANDED_BLUNT, ONEHANDED_AXE, TWOHANDED_AXE, SHIELD }, // 디펜더
|
||||
{ CON_HELM, CON_GLOVE, CON_BOOTS, CON_ARMOUR, ONEHANDED_SWORD, TWOHANDED_SWORD, ONEHANDED_AXE, TWOHANDED_AXE, MAX_ITEM_TYPE }, // 워리어
|
||||
{ DEX_HELM, DEX_GLOVE, DEX_BOOTS, DEX_ARMOUR, DAGGER, ONEHANDED_SWORD, ONEHANDED_AXE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 어쌔신
|
||||
{ DEX_HELM, DEX_GLOVE, DEX_BOOTS, DEX_ARMOUR, DAGGER, BOW, CROSSBOW, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 아처
|
||||
{ DEX_HELM, DEX_GLOVE, DEX_BOOTS, DEX_ARMOUR, STAFF, DAGGER, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 소서러
|
||||
{ DEX_HELM, DEX_GLOVE, DEX_BOOTS, DEX_ARMOUR, STAFF, BOW, CROSSBOW, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 엔찬터
|
||||
{ CON_HELM, CON_GLOVE, CON_BOOTS, CON_ARMOUR, ONEHANDED_BLUNT, TWOHANDED_BLUNT, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 프리스트
|
||||
{ CON_HELM, CON_GLOVE, CON_BOOTS, CON_ARMOUR, ONEHANDED_BLUNT, TWOHANDED_BLUNT, SHIELD, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 클레릭
|
||||
|
||||
{ MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE },
|
||||
{ MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE },
|
||||
{ MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE },
|
||||
{ MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE },
|
||||
|
||||
{ CON_HEAD, CON_PROTECT_A, CON_PELVIS, CON_BODY, COM_SWORD, COM_BLUNT, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 컴배턴트
|
||||
{ DEX_HEAD, DEX_PROTECT_A, DEX_PELVIS, DEX_BODY, OPP_AXE, OPP_SLUSHER, OPP_TALON, OPP_SYTHE, MAX_ITEM_TYPE }, // 오피세이터
|
||||
{ CON_HEAD, CON_PROTECT_A, CON_PELVIS, CON_BODY, COM_SWORD, COM_BLUNT, SKILL_A_GUARD, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 템플러
|
||||
{ CON_HEAD, CON_PROTECT_A, CON_PELVIS, CON_BODY, COM_SWORD, COM_BLUNT, SKILL_A_ATTACK, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 어태커
|
||||
{ CON_HEAD, CON_PROTECT_A, CON_PELVIS, CON_BODY, COM_SWORD, COM_BLUNT, SKILL_A_GUN, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 거너
|
||||
{ DEX_HEAD, DEX_PROTECT_A, DEX_PELVIS, DEX_BODY, OPP_SYTHE, OPP_SLUSHER, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 룬오프
|
||||
{ DEX_HEAD, DEX_PROTECT_A, DEX_PELVIS, DEX_BODY, OPP_HAMMER, OPP_AXE, MAX_ITEM_TYPE, MAX_ITEM_TYPE, MAX_ITEM_TYPE }, // 라이프오프
|
||||
{ DEX_HEAD, DEX_PROTECT_A, DEX_PELVIS, DEX_BODY, OPP_TALON, OPP_SLUSHER, SKILL_A_KNIFE, MAX_ITEM_TYPE, MAX_ITEM_TYPE } // 쉐도우오프
|
||||
};
|
||||
|
||||
const unsigned char aryHumanEquipTypeList[HUMAN_EQUIP_TYPE_NUM] = {
|
||||
CON_ARMOUR, CON_HELM, CON_GLOVE, CON_BOOTS,
|
||||
DEX_ARMOUR, DEX_HELM, DEX_GLOVE, DEX_BOOTS,
|
||||
|
||||
ONEHANDED_SWORD, TWOHANDED_SWORD, ONEHANDED_AXE, TWOHANDED_AXE,
|
||||
ONEHANDED_BLUNT, TWOHANDED_BLUNT, BOW, CROSSBOW,
|
||||
STAFF, DAGGER, SHIELD
|
||||
};
|
||||
|
||||
const unsigned char aryAkhanEquipTypeList[AKHAN_EQUIP_TYPE_NUM] = {
|
||||
CON_BODY, CON_HEAD, CON_PELVIS, CON_PROTECT_A,
|
||||
DEX_BODY, DEX_HEAD, DEX_PELVIS, DEX_PROTECT_A,
|
||||
|
||||
COM_BLUNT, COM_SWORD, OPP_HAMMER, OPP_AXE,
|
||||
OPP_SLUSHER, OPP_TALON, OPP_SYTHE,
|
||||
|
||||
SKILL_A_GUARD, SKILL_A_ATTACK, SKILL_A_GUN, SKILL_A_KNIFE
|
||||
};
|
||||
|
||||
|
||||
class CAward : public CSingleton<CAward>
|
||||
{
|
||||
public:
|
||||
|
||||
unsigned long GetAward(unsigned char cItemKind, CMonster* lpDeadMonster, CCharacter* lpAttackCharacter,
|
||||
Item::CItem** lpItem_Out);
|
||||
|
||||
unsigned short GetAwardEquipment(Item::EquipType::OptionType eOptionType,
|
||||
unsigned char cDeadMonsterLevel, CCharacter* lpAttackCharacter, bool bQuestAward);
|
||||
|
||||
unsigned short GetQuestEquipmentClass(Item::EquipType::OptionType eOptionType, unsigned char cClass,
|
||||
unsigned char cDeadMonsterLevel, CCharacter* lpAttackCharacter);
|
||||
|
||||
private:
|
||||
|
||||
unsigned long GetAwardCoin(unsigned char cDeadMonsterLevel, float cAddPer);
|
||||
unsigned long GetAwardGem(unsigned char cDeadMonsterLevel);
|
||||
unsigned long GetAwardMetal(unsigned char cDeadMonsterLevel);
|
||||
unsigned long GetAwardPotion(unsigned char cDeadMonsterLevel);
|
||||
unsigned long GetAwardSkill(unsigned char cDeadMonsterLevel, char cAttackCharacterNation);
|
||||
unsigned long GetAwardCashItem(char cAttackCharacterNation, unsigned char cDeadMonsterLevel, unsigned int cDeadMonsterPattern);
|
||||
unsigned long GetAwardLottery(void);
|
||||
|
||||
//--// start..
|
||||
unsigned long GetAwardRing(unsigned char cLevel, unsigned char cGrade);
|
||||
unsigned long GetAwardNecklace(unsigned char cLevel, unsigned char cGrade);
|
||||
unsigned long GetAwardRune(unsigned char cLevel, unsigned char cGrade);
|
||||
unsigned long GetAwardDestructionRune();
|
||||
//--// end..
|
||||
|
||||
unsigned short SelectItemByRace(Item::EquipType::OptionType eOptionType,
|
||||
const unsigned char* aryItemType, unsigned char cMaxItemType, unsigned char cLevel);
|
||||
|
||||
static CAward ms_this;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
// FSM.cpp: implementation of the CFSM2 class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <algorithm>
|
||||
#include "FSM.h"
|
||||
#include "FSMState.h"
|
||||
|
||||
CFSM CFSM::ms_this;
|
||||
|
||||
CFSM::CFSM(void)
|
||||
: m_nStateNum(0)
|
||||
{
|
||||
std::fill_n(m_lpFSMState, int(MAX_STATE), reinterpret_cast<CFSMState*>(0));
|
||||
}
|
||||
|
||||
CFSM::~CFSM()
|
||||
{
|
||||
CFSMState** lppFSMPastEnd = m_lpFSMState + MAX_STATE;
|
||||
for(CFSMState** lppFSM = m_lpFSMState; lppFSM != lppFSMPastEnd; ++lppFSM)
|
||||
{
|
||||
delete *lppFSM;
|
||||
*lppFSM = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int CFSM::StateTransition(int nCrurrentState, int Input)
|
||||
{
|
||||
if (!nCrurrentState)
|
||||
{
|
||||
return nCrurrentState;
|
||||
}
|
||||
|
||||
CFSMState* lpState = GetState(nCrurrentState);
|
||||
if (NULL == lpState)
|
||||
{
|
||||
return nCrurrentState;
|
||||
}
|
||||
|
||||
nCrurrentState = lpState->GetOutput(Input);
|
||||
return nCrurrentState;
|
||||
}
|
||||
|
||||
|
||||
CFSMState* CFSM::GetState(int StateID)
|
||||
{
|
||||
CFSMState** lppFSMPastEnd = m_lpFSMState + MAX_STATE;
|
||||
for(CFSMState** lppFSM = m_lpFSMState; lppFSM != lppFSMPastEnd; ++lppFSM)
|
||||
{
|
||||
if(NULL == *lppFSM)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(StateID == (*lppFSM)->GetID())
|
||||
{
|
||||
return *lppFSM;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool CFSM::AddState(CFSMState* lpNewState)
|
||||
{
|
||||
if(m_nStateNum >= MAX_STATE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lpFSMState[m_nStateNum++] = lpNewState;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// FSM.h: interface for the CFSM class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_FSM_H__5BDFA770_7869_4B08_B0BC_D7C6AC789C9D__INCLUDED_)
|
||||
#define AFX_FSM_H__5BDFA770_7869_4B08_B0BC_D7C6AC789C9D__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
|
||||
#include <Pattern/Singleton.h>
|
||||
#include "FSMState.h"
|
||||
|
||||
|
||||
enum MONSTER_STATE_OR_INPUT
|
||||
{
|
||||
STATE_ID_NULL = 0,
|
||||
STATE_ID_NORMAL,
|
||||
STATE_ID_ATTACK,
|
||||
STATE_ID_RETURN,
|
||||
STATE_ID_ESCAPE,
|
||||
STATE_ID_DIE,
|
||||
|
||||
INPUT_ID_SEEN_PLAYER = 100,
|
||||
INPUT_ID_LOW_HP,
|
||||
INPUT_ID_ZERO_HP,
|
||||
INPUT_ID_LEAVE_PLAYER,
|
||||
INPUT_ID_ARRIVAL_SITE,
|
||||
INPUT_ID_ATTACKED_PLAYER,
|
||||
INPUT_ID_CMD_ATTACK,
|
||||
INPUT_ID_CMD_ESCAPE
|
||||
};
|
||||
|
||||
|
||||
class CFSM : public CSingleton<CFSM>
|
||||
{
|
||||
public:
|
||||
|
||||
enum { MAX_STATE = 8 };
|
||||
|
||||
~CFSM(void); // clean up memory usage
|
||||
|
||||
bool AddState(CFSMState* lpNewState); // add a FSMstate object pointer to the map
|
||||
CFSMState* GetState(int StateID); // return the FSMstate object pointer
|
||||
|
||||
int StateTransition(int nCrurrentState, int Input); // perform a state transition based on input and current state
|
||||
|
||||
protected:
|
||||
|
||||
CFSM(void); // set initial state of the FSM
|
||||
|
||||
int m_nStateNum;
|
||||
CFSMState* m_lpFSMState[MAX_STATE]; // map containing all states of this FSM
|
||||
|
||||
static CFSM ms_this;
|
||||
};
|
||||
|
||||
|
||||
#endif // !defined(AFX_FSM_H__5BDFA770_7869_4B08_B0BC_D7C6AC789C9D__INCLUDED_)
|
||||
@@ -0,0 +1,130 @@
|
||||
// FSMState.cpp: implementation of the CFSMState2 class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "FSMState.h"
|
||||
|
||||
|
||||
CFSMState::CFSMState(int StateID, int Transitions)
|
||||
: m_dwStateID(StateID), m_dwNumberOfTransistions((Transitions) ? Transitions : 1)
|
||||
{
|
||||
m_pdwInputs = new int[m_dwNumberOfTransistions];
|
||||
if (NULL == m_pdwInputs)
|
||||
{
|
||||
ERRLOG1(g_Log, "m_pdwInputs의 메모리 할당에 실패하였습니다. 할당 개수는 %d개 입니다.",
|
||||
m_dwNumberOfTransistions);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill_n(m_pdwInputs, m_dwNumberOfTransistions, 0);
|
||||
|
||||
m_pdwOutputState = new int[m_dwNumberOfTransistions];
|
||||
if (NULL == m_pdwOutputState)
|
||||
{
|
||||
ERRLOG1(g_Log, "m_pdwOutputState의 메모리 할당에 실패하였습니다. 할당 개수는 %d개 입니다.",
|
||||
m_dwNumberOfTransistions);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill_n(m_pdwOutputState, m_dwNumberOfTransistions, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFSMState::~CFSMState()
|
||||
{
|
||||
if(NULL != m_pdwInputs)
|
||||
{
|
||||
delete m_pdwInputs;
|
||||
m_pdwInputs = NULL;
|
||||
}
|
||||
|
||||
if(NULL != m_pdwOutputState)
|
||||
{
|
||||
delete m_pdwOutputState;
|
||||
m_pdwOutputState = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CFSMState::AddTransition(int Input, int OutputID)
|
||||
{
|
||||
int nTransition = 0;
|
||||
for(; nTransition < m_dwNumberOfTransistions; ++nTransition)
|
||||
{
|
||||
if(0 == m_pdwOutputState[nTransition])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(nTransition >= m_dwNumberOfTransistions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pdwInputs[nTransition] = Input;
|
||||
m_pdwOutputState[nTransition] = OutputID;
|
||||
}
|
||||
|
||||
|
||||
int CFSMState::GetOutput(int Input)
|
||||
{
|
||||
int OutputID = m_dwStateID; // output state to be returned
|
||||
|
||||
for(int nTransition = 0;
|
||||
nTransition < m_dwNumberOfTransistions; ++nTransition)
|
||||
{
|
||||
if(0 == m_pdwOutputState[nTransition])
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(Input == m_pdwInputs[nTransition])
|
||||
{
|
||||
OutputID = m_pdwOutputState[nTransition]; // output state id
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return OutputID;
|
||||
}
|
||||
|
||||
|
||||
void CFSMState::DeleteTransition(int OutputID)
|
||||
{
|
||||
int nTransition = 0;
|
||||
for(;nTransition < m_dwNumberOfTransistions; ++nTransition)
|
||||
{
|
||||
if(OutputID == m_pdwOutputState[nTransition])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(nTransition >= m_dwNumberOfTransistions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pdwInputs[nTransition] = 0;
|
||||
m_pdwOutputState[nTransition] = 0;
|
||||
|
||||
for(;nTransition < (m_dwNumberOfTransistions - 1); ++nTransition)
|
||||
{
|
||||
if (!m_pdwOutputState[nTransition])
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
m_pdwInputs[nTransition] = m_pdwInputs[nTransition+1];
|
||||
m_pdwOutputState[nTransition] = m_pdwOutputState[nTransition+1];
|
||||
}
|
||||
|
||||
m_pdwInputs[nTransition] = 0;
|
||||
m_pdwOutputState[nTransition] = 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// FSMState.h: interface for the CFSMState class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_FSMSTATE_H__4D5ECC39_C7A5_47E7_A774_9E79707B18D8__INCLUDED_)
|
||||
#define AFX_FSMSTATE_H__4D5ECC39_C7A5_47E7_A774_9E79707B18D8__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
|
||||
class CFSMState
|
||||
{
|
||||
private:
|
||||
|
||||
int m_dwNumberOfTransistions; // maximum number of states supported by this state
|
||||
int *m_pdwInputs; // input array for tranistions
|
||||
int *m_pdwOutputState; // output state array
|
||||
int m_dwStateID; // the unique ID of this state
|
||||
|
||||
int FindTransitionIndex(int nID);
|
||||
|
||||
public:
|
||||
|
||||
explicit CFSMState(int StateID, int Transitions);
|
||||
~CFSMState(void);
|
||||
|
||||
void AddTransition(int Input, int OutputID);
|
||||
void DeleteTransition(int OutputID);
|
||||
|
||||
int GetOutput(int Input);
|
||||
|
||||
inline int GetID(void) { return m_dwStateID; }
|
||||
};
|
||||
|
||||
|
||||
#endif // !defined(AFX_FSMSTATE_H__4D5ECC39_C7A5_47E7_A774_9E79707B18D8__INCLUDED_)
|
||||
@@ -0,0 +1,898 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Purpose : 몬스터 정보를 저장 & 관리하는 클래스
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Utility/Registry/RegFunctions.h>
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <Utility/Time/Pulse/Pulse.h>
|
||||
#include <Utility/DelimitedFile.h>
|
||||
#include <Utility/Setup/ServerSetup.h>
|
||||
|
||||
#include <Network/ClientSocket/ClientConstants.h>
|
||||
#include <Network/Dispatch/GameClient/SendCharAttack.h>
|
||||
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
|
||||
#include <Network/Packet/PacketStruct/ServerInfo.h>
|
||||
#include <Network/Packet/WrapPacket.h>
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/Monster/PatternMonster.h>
|
||||
#include <Creature/Siege/SiegeObject.h>
|
||||
#include <Creature/EnemyCheck.h>
|
||||
|
||||
#include <Community/Party/Party.h>
|
||||
#include <Community/Party/PartyMgr.h>
|
||||
#include <Community/Guild/Guild.h>
|
||||
#include <Community/Guild/GuildMgr.h>
|
||||
|
||||
#include <Castle/Castle.h>
|
||||
#include <Castle/CastleMgr.h>
|
||||
|
||||
#include <GameTime/GameTimeConstants.h>
|
||||
#include <GameTime/GameTimeMgr.h>
|
||||
|
||||
#include <Map/FieldMap/CellManager.h>
|
||||
#include <Skill/Spell/Spell.h>
|
||||
#include <Log/CharacterLog.h>
|
||||
|
||||
#include "AwardTable.h"
|
||||
#include "Monster.h"
|
||||
#include "MonsterShout.h"
|
||||
|
||||
|
||||
LONG CMonster::ms_NormalBehaviorSendCount = 0;
|
||||
LONG CMonster::ms_AttackBehaviorSendCount = 0;
|
||||
LONG CMonster::ms_ReturnBehaviorSendCount = 0;
|
||||
LONG CMonster::ms_EscapeBehaviorSendCount = 0;
|
||||
LONG CMonster::ms_DeadBehaviorSendCount = 0;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
CMonster::CMonster()
|
||||
: m_lpTarget(NULL), m_dwLastBehaviorTick(0), m_lCurrentFrame(0), m_bAttacking(false), m_nCurrentState(0), m_wSearchRange(0),
|
||||
m_nNormalMovingDelay(0), m_nLeaveMovingNum(0), m_bAvoid(false), m_bLongRangeAttacked(false), m_bAdminCmdSummon(false),
|
||||
m_bScout(false), m_nMovingPattern(0), m_OriginalPosition(), CAggresiveCreature(0), m_dwPID(0), m_wRespawnArea(0)
|
||||
{
|
||||
m_wDefaultSearchRange = MONSTER_SEARCH_RANGE;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::CMonster
|
||||
//
|
||||
// Description : 사용자 생성자
|
||||
//
|
||||
// Inputs : MonsterCreate - 몬스터 생성 정보
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : None.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
CMonster::CMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon)
|
||||
: m_lpTarget(NULL), m_dwLastBehaviorTick(0), m_lCurrentFrame(0), m_bAttacking(false), m_nCurrentState(0), m_wSearchRange(0),
|
||||
m_nNormalMovingDelay(0), m_nLeaveMovingNum(0), m_bAvoid(false), m_bLongRangeAttacked(false), m_bAdminCmdSummon(bAdminCmdSummon),
|
||||
m_bScout(MonsterCreate.m_bScout), m_nMovingPattern(MonsterCreate.m_nMovingPattern), m_wRespawnArea(MonsterCreate.m_wRespawnArea),
|
||||
m_OriginalPosition(MonsterCreate.m_Pos), CAggresiveCreature(MonsterCreate.m_dwCID), m_dwPID(MonsterCreate.m_dwPID)
|
||||
{
|
||||
const CMonsterMgr::MonsterProtoType* pProtoType =
|
||||
CMonsterMgr::GetInstance().GetMonsterProtoType(MonsterCreate.m_nKID);
|
||||
|
||||
if (NULL == pProtoType)
|
||||
{
|
||||
ERRLOG1(g_Log, "알맞은 프로토타입이 없습니다. MonsterProtoType.txt를 확인해주십시오. KID:%d", MonsterCreate.m_nKID);
|
||||
return;
|
||||
}
|
||||
|
||||
m_CreatureStatus = pProtoType->m_CreatureStatus;
|
||||
m_MonsterInfo = pProtoType->m_MonsterInfo;
|
||||
|
||||
m_CreatureStatus.m_StatusInfo.CalculateSubStatus();
|
||||
|
||||
// 게임중에 챈트 효과 계산을 위해 존재합니다.
|
||||
m_EquipStatus = pProtoType->m_CreatureStatus.m_StatusInfo;
|
||||
m_EquipStatus.m_cCalculateState = FightStatus::CS_EQUIP_INFO;
|
||||
|
||||
m_wDefaultSearchRange = MONSTER_SEARCH_RANGE;
|
||||
}
|
||||
|
||||
|
||||
CMonster::~CMonster()
|
||||
{
|
||||
m_SpellMgr.GetAffectedInfo().ClearAll();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::InitMonster
|
||||
//
|
||||
// Description : 몬스터 초기화
|
||||
//
|
||||
// Inputs : Pos - 몬스터의 위치
|
||||
// bDead - 처음 로긴 때는 true, 리스폰 시엔 false
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : 성공 여부.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::InitMonster(Position &Pos, bool bDead)
|
||||
{
|
||||
m_CellPos.MoveTo(Pos);
|
||||
if (NULL == m_CellPos.m_lpCell)
|
||||
{
|
||||
ERRLOG4(g_Log, "CID:0x%08x 범위를 벗어난 셀에서 몬스터가 로긴하였습니다. X:%.1f, Y:%.1f, Z:%.1f",
|
||||
m_dwCID, Pos.m_fPointX, Pos.m_fPointY, Pos.m_fPointZ);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_CurrentPos = Pos;
|
||||
m_wSearchRange = m_wDefaultSearchRange;
|
||||
|
||||
m_lpTarget = NULL;
|
||||
|
||||
m_lCurrentFrame = 0;
|
||||
m_bAttacking = false;
|
||||
m_dwLastBehaviorTick = m_dwLastTime = CPulse::GetInstance().GetLastTick();
|
||||
|
||||
ZeroMemory(&m_MotionInfo, sizeof(m_MotionInfo));
|
||||
|
||||
if (bDead)
|
||||
{
|
||||
m_nCurrentState = STATE_ID_DIE;
|
||||
m_CreatureStatus.m_nNowHP = 0;
|
||||
m_CreatureStatus.m_nNowMP = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CellPos.m_lpCell->SetCreature(m_dwCID, this);
|
||||
|
||||
m_nCurrentState = STATE_ID_NORMAL;
|
||||
m_CreatureStatus.m_nNowHP = m_CreatureStatus.m_StatusInfo.m_nMaxHP;
|
||||
m_CreatureStatus.m_nNowMP = m_CreatureStatus.m_StatusInfo.m_nMaxMP;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::GetMotion
|
||||
//
|
||||
// Description : 모션 정보를 얻음
|
||||
//
|
||||
// Inputs : MotionID - 모션 ID
|
||||
//
|
||||
// Outputs : Motion - 모션 정보
|
||||
//
|
||||
// Returns : bool - 정보가 없는 ID면 false.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::GetMotion(unsigned long MotionID, MotionInfo &Motion)
|
||||
{
|
||||
int nIndex = 0;
|
||||
|
||||
switch (MotionID)
|
||||
{
|
||||
case MonsterInfo::Z3D_CA_WALK: nIndex = 0; break;
|
||||
case MonsterInfo::Z3D_CA_RUN: nIndex = 1; break;
|
||||
case MonsterInfo::Z3D_CA_ATTACK: nIndex = 2; break;
|
||||
case MonsterInfo::Z3D_CA_CASTING: nIndex = 3; break;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
|
||||
// 방향은 복사하면 안 된다.
|
||||
Motion.m_wAction = m_MonsterInfo.m_MonsterMotions[nIndex].m_wAction;
|
||||
Motion.m_dwFrame = m_MonsterInfo.m_MonsterMotions[nIndex].m_dwFrame;
|
||||
Motion.m_fVelocity = m_MonsterInfo.m_MonsterMotions[nIndex].m_fVelocity;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::IsOverlap
|
||||
//
|
||||
// Description : 몬스터가 겹쳐져 있는가?
|
||||
//
|
||||
// Inputs : None.
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : bool - 위 질문에 대한 Yes/No
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::IsOverlap(void)
|
||||
{
|
||||
for (int nCellCount = 0; nCellCount < CCell::CONNECT_NUM; ++nCellCount)
|
||||
{
|
||||
CCell* pCell = m_CellPos.m_lpCell->GetConnectCell(nCellCount);
|
||||
if (NULL == pCell || false == pCell->IsMonster())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CMonster* lpTempMonster = pCell->GetFirstMonster();
|
||||
|
||||
while (NULL != lpTempMonster)
|
||||
{
|
||||
if (this != lpTempMonster)
|
||||
{
|
||||
const float fDX = lpTempMonster->GetCurrentPos().m_fPointX - GetCurrentPos().m_fPointX;
|
||||
const float fDZ = lpTempMonster->GetCurrentPos().m_fPointZ - GetCurrentPos().m_fPointZ;
|
||||
|
||||
if (fDX * fDX + fDZ * fDZ <= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
lpTempMonster = pCell->GetNextMonster();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::UpdateBehavior
|
||||
//
|
||||
// Description : 몬스터의 행동 설정
|
||||
//
|
||||
// Inputs : dwTick - 현재 틱카운트
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : None.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
void CMonster::UpdateBehavior(unsigned long dwTick)
|
||||
{
|
||||
// 네임드 몬스터는 스턴/석화에 걸리지 않는다.
|
||||
if (MonsterInfo::PATTERN_NAMED == m_MonsterInfo.m_cSkillPattern)
|
||||
{
|
||||
m_SpellMgr.GetAffectedInfo().RemoveEnchantBySpellType(Skill::SpellID::Stun);
|
||||
m_SpellMgr.GetAffectedInfo().RemoveEnchantBySpellType(Skill::SpellID::StoneForm);
|
||||
}
|
||||
|
||||
// 스턴/석화시엔 아무 행동도 하지 않는다.
|
||||
if (GetEnchantInfo().GetFlag(Skill::SpellID::Stun) ||
|
||||
GetEnchantInfo().GetFlag(Skill::SpellID::StoneForm))
|
||||
{
|
||||
m_lCurrentFrame = FPS;
|
||||
return;
|
||||
}
|
||||
|
||||
// 상태에 따른 몬스터 행동 처리를 한다.
|
||||
switch (m_nCurrentState)
|
||||
{
|
||||
case STATE_ID_NORMAL: NormalBehavior(dwTick); break; // 보통 상태
|
||||
case STATE_ID_ATTACK: AttackBehavior(dwTick); break; // 공격 상태 (거리가 멀어지면 쫓아가고...)
|
||||
case STATE_ID_RETURN: ReturnBehavior(dwTick); break; // 리턴 상태 (제 위치로 돌아가는...)
|
||||
case STATE_ID_ESCAPE: EscapeBehavior(); break; // 도망 상태
|
||||
case STATE_ID_DIE: DeadBehavior(dwTick); break; // 죽은 상태 (리스폰 시간이 지나면 리스폰 하지~)
|
||||
}
|
||||
|
||||
/*
|
||||
// 몬스터 정보 로그
|
||||
char logString[MAX_PATH];
|
||||
switch (m_nCurrentState)
|
||||
{
|
||||
case STATE_ID_NORMAL: strcpy(logString, "Normal"); break;
|
||||
case STATE_ID_ATTACK: strcpy(logString, "Attack"); break;
|
||||
case STATE_ID_RETURN: strcpy(logString, "Return"); break;
|
||||
case STATE_ID_ESCAPE: strcpy(logString, "Escape"); break;
|
||||
case STATE_ID_DIE: strcpy(logString, "Die"); break;
|
||||
}
|
||||
|
||||
DETLOG3(g_Log, "몬스터 정보 로그 - * State : %s\t* fX : %f\t* fZ : %f",
|
||||
logString, GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::Process
|
||||
//
|
||||
// Description : 몬스터 프로세싱
|
||||
//
|
||||
// Inputs : None.
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : bool - 모션 출력 중엔 false
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::Process()
|
||||
{
|
||||
const unsigned long dwTick = CPulse::GetInstance().GetLastTick();
|
||||
|
||||
if (dwTick - m_dwLastBehaviorTick < FPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((GetSerialNumber() % 3) != (CPulse::GetInstance().GetCurrentPulse() % 3))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned long dwFrame = ((dwTick - m_dwLastBehaviorTick) / FPS);
|
||||
|
||||
// 공격 모션은 따로 처리합니다. (이동하면서 공격 가능)
|
||||
if (m_lCurrentFrame <= 0 || m_bAttacking == true)
|
||||
{
|
||||
if (m_lCurrentFrame <= 0 && m_bAttacking == true) {
|
||||
m_bAttacking = false;
|
||||
}
|
||||
|
||||
UpdateBehavior(dwTick);
|
||||
}
|
||||
|
||||
// 틱 갱신
|
||||
m_dwLastBehaviorTick = dwTick;
|
||||
|
||||
unsigned long dwSlowlyRate = (true == GetEnchantInfo().GetFlag(Skill::SpellID::Frozen)) ? 2 : 1;
|
||||
|
||||
m_lCurrentFrame -= (dwFrame / dwSlowlyRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::MultiAttack
|
||||
//
|
||||
// Description : 몬스터 범위 공격 시 범위 내 타겟을 검색
|
||||
//
|
||||
// Inputs : None.
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : None.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::MultiAttack(void)
|
||||
{
|
||||
unsigned char cDefenderNum = 1;
|
||||
CAggresiveCreature* ppAggresiveCreature[AtNode::MAX_DEFENDER_NUM];
|
||||
unsigned char nDefenserJudges[AtNode::MAX_DEFENDER_NUM];
|
||||
|
||||
ppAggresiveCreature[0] = m_lpTarget;
|
||||
|
||||
// TODO : 공격하는 방향을 설정합시다. (현재는 무조건 정면)
|
||||
short nMaxDefenderNum = AtNode::MAX_DEFENDER_NUM;
|
||||
std::fill_n(&nDefenserJudges[0], nMaxDefenderNum, ClientConstants::Judge_Front);
|
||||
|
||||
float fDir = CalcDir2D(GetCurrentPos().m_fPointX, GetCurrentPos().m_fPointZ,
|
||||
m_lpTarget->GetCurrentPos().m_fPointX, m_lpTarget->GetCurrentPos().m_fPointZ);
|
||||
|
||||
AtType attackType;
|
||||
attackType.m_wType = AtType::RIGHT_MELEE;
|
||||
char cTargetType = Skill::Target::ENEMY;
|
||||
|
||||
return CAggresiveCreature::MultiAttack(attackType, cDefenderNum, ppAggresiveCreature, nDefenserJudges,
|
||||
GetCurrentPos(), fDir, m_MonsterInfo.m_wAttackRange / 100.0f, m_MonsterInfo.m_fAttackAngle, cTargetType);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::CancelTarget
|
||||
//
|
||||
// Description : 타겟의 취소
|
||||
//
|
||||
// Inputs : bool bSaveThreat - 맞은 쓰레트 값을 유지하는가?
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : None.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
void CMonster::CancelTarget(bool bSaveThreat)
|
||||
{
|
||||
if (NULL != m_lpTarget)
|
||||
{
|
||||
if (!bSaveThreat)
|
||||
{
|
||||
m_lpTarget->GetThreat().DeleteThreatened(this);
|
||||
m_Threat.DeleteThreat(m_lpTarget);
|
||||
}
|
||||
|
||||
// 파티가 있었다면.. 파티의 타겟에서 제거한다.
|
||||
CMonsterParty* lpParty = reinterpret_cast<CMonsterParty*>(GetParty());
|
||||
if (NULL != lpParty)
|
||||
{
|
||||
CMonsterParty::PartyTargetSet::iterator itr = lpParty->GetPartyTargetSet().find(m_lpTarget->GetCID());
|
||||
if (itr != lpParty->GetPartyTargetSet().end())
|
||||
{
|
||||
lpParty->GetPartyTargetSet().erase(itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_lpTarget = NULL;
|
||||
m_bAttacking = false;
|
||||
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_LEAVE_PLAYER);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::Dead
|
||||
//
|
||||
// Description : 몬스터의 사망 처리
|
||||
//
|
||||
// Inputs : None.
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : 성공 여부. (false면 타겟도 없는 주제에 죽은 경우)
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::Dead(CAggresiveCreature* pOffencer)
|
||||
{
|
||||
m_dwLastBehaviorTick = m_dwLastTime = CPulse::GetInstance().GetLastTick();
|
||||
m_lCurrentFrame = FPS;
|
||||
m_bAttacking = false;
|
||||
|
||||
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ZERO_HP);
|
||||
|
||||
// 어워드 처리
|
||||
unsigned long aryItemID[AwardTable::MAX_DROP_ITEM + EliteBonus::MAX_BONUS_DROP_ITEM] = { 0, };
|
||||
|
||||
Item::CItem* aryItem[AwardTable::MAX_DROP_ITEM + EliteBonus::MAX_BONUS_DROP_ITEM] = { 0, };
|
||||
|
||||
unsigned long dwOwnerID = 0;
|
||||
unsigned char cItemNum = m_Threat.GetAward(aryItemID, aryItem, &dwOwnerID);
|
||||
|
||||
Position SetPosition;
|
||||
CCell::ItemInfo itemInfo;
|
||||
|
||||
CCell* lpDropCell = GetCellPos().m_lpCell;
|
||||
|
||||
if (NULL != lpDropCell)
|
||||
{
|
||||
for (unsigned char cDropIndex = 0; cDropIndex < cItemNum; ++cDropIndex)
|
||||
{
|
||||
if (0 != aryItemID[cDropIndex])
|
||||
{
|
||||
SetPosition = GetCurrentPos();
|
||||
SetPosition.m_fPointX += cDropIndex;
|
||||
if (cItemNum / 2 <= cDropIndex)
|
||||
{
|
||||
SetPosition.m_fPointX -= cItemNum / 2;
|
||||
SetPosition.m_fPointZ += 1.0f;
|
||||
}
|
||||
|
||||
unsigned long dwGold = (NULL == aryItem[cDropIndex]) ? (aryItemID[cDropIndex] & ~CCell::TYPE_CHECK_BIT) : 0;
|
||||
|
||||
if(pOffencer->GetStatus().m_nLevel<=GOLD_INC_LEVEL_LIMIT)
|
||||
{
|
||||
dwGold = static_cast<unsigned long>( dwGold * 1.5f );
|
||||
}
|
||||
|
||||
lpDropCell->SetItem(SetPosition, aryItem[cDropIndex], dwGold, dwOwnerID, CCell::PARTY, itemInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 경험치를 분배한다.
|
||||
const long lMaxThreat = m_Threat.GetMaxThreatAmount();
|
||||
m_Threat.DivisionExp();
|
||||
|
||||
// 셀에서 몬스터를 일단 제거한다.
|
||||
m_CellPos.m_lpCell->DeleteCreature(m_dwCID);
|
||||
|
||||
CAggresiveCreature *pCreature = m_Threat.GetMaxThreatCreature();
|
||||
if (NULL != pCreature)
|
||||
{
|
||||
if (m_CreatureStatus.m_nLevel - pCreature->GetStatus().m_nLevel >= 3 &&
|
||||
lMaxThreat > m_CreatureStatus.m_StatusInfo.m_nMaxHP * 0.8f)
|
||||
{ // 레벨차가 3 이상 나는 몬스터를 잡았을 경우 로그를 찍음
|
||||
RULLOG5(g_Log, "몬스터께서 돌아가셨습니다... Monster : 0x%08x (%d), Player : 0x%08x (%d), 획득 경험치 비율 : %.1f%%",
|
||||
m_dwCID, m_CreatureStatus.m_nLevel, m_Threat.GetMaxThreatCreature()->GetCID(),
|
||||
m_Threat.GetMaxThreatCreature()->GetStatus().m_nLevel, lMaxThreat * 100.0f / m_CreatureStatus.m_StatusInfo.m_nMaxHP);
|
||||
}
|
||||
|
||||
CCharacter* lpOffencerCharacter =
|
||||
(Creature::CT_PC == Creature::GetCreatureType(pCreature->GetCID()))
|
||||
? static_cast<CCharacter*>(pCreature) : 0;
|
||||
|
||||
if (0 != lpOffencerCharacter)
|
||||
{
|
||||
// 퀘스트 트리거 발동
|
||||
lpOffencerCharacter->CheckTrigger(Quest::TRIGGER_KILL, m_MonsterInfo.m_dwKID, m_CurrentPos, 1);
|
||||
|
||||
// 몬스터 살해 로그를 남긴다. 남기는 항목은 몬스터 레벨/떨군 아이템ID 및 등급, 개수등이다
|
||||
GAMELOG::LogMonsterDead(*lpOffencerCharacter, GetCID(),
|
||||
m_CreatureStatus.m_nLevel, aryItemID, cItemNum);
|
||||
}
|
||||
}
|
||||
|
||||
// 파티에서 제거
|
||||
CParty* lpParty = GetParty();
|
||||
if (NULL != lpParty)
|
||||
{
|
||||
lpParty->Leave(GetCID(), 0, GetMapIndex());
|
||||
}
|
||||
|
||||
return CAggresiveCreature::Dead(pOffencer);
|
||||
}
|
||||
|
||||
|
||||
EnemyCheck::EnemyType CMonster::IsEnemy(CCreature* lpTarget, unsigned char* cResult)
|
||||
{
|
||||
if (NULL != lpTarget)
|
||||
{
|
||||
switch (Creature::GetCreatureType(lpTarget->GetCID()))
|
||||
{
|
||||
case Creature::CT_PC:
|
||||
case Creature::CT_SUMMON:
|
||||
case Creature::CT_SIEGE_OBJECT:
|
||||
{
|
||||
return lpTarget->IsEnemy(this);
|
||||
}
|
||||
|
||||
case Creature::CT_NPC:
|
||||
case Creature::CT_MONSTER:
|
||||
case Creature::CT_STRUCT:
|
||||
{
|
||||
if (GetNation() == lpTarget->GetNation())
|
||||
{
|
||||
return EnemyCheck::EC_FRIEND;
|
||||
}
|
||||
|
||||
return EnemyCheck::EC_ENEMY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ERRLOG1(g_Log, "CID:0x%08x 피아식별할 타겟이 없습니다.", m_dwCID);
|
||||
return EnemyCheck::EC_NEUTRAL;
|
||||
}
|
||||
|
||||
|
||||
void CMonster::Respawn(unsigned long dwTick)
|
||||
{
|
||||
Position RespawnPos(m_OriginalPosition.m_fPointX, m_OriginalPosition.m_fPointY, m_OriginalPosition.m_fPointZ);
|
||||
|
||||
if (m_nMovingPattern != FIXED && m_wRespawnArea > 0)
|
||||
{
|
||||
RespawnPos.m_fPointX += static_cast<float>(Math::Random::SimpleRandom(dwTick, m_wRespawnArea*2) - m_wRespawnArea);
|
||||
RespawnPos.m_fPointY += m_OriginalPosition.m_fPointY;
|
||||
RespawnPos.m_fPointZ += static_cast<float>(Math::Random::SimpleRandom(dwTick, m_wRespawnArea*2) - m_wRespawnArea);
|
||||
}
|
||||
|
||||
InitMonster(RespawnPos);
|
||||
m_nCurrentState = STATE_ID_NORMAL;
|
||||
|
||||
// 파티에 추가
|
||||
CParty* lpParty = CPartyMgr::GetInstance().GetParty(GetPID());
|
||||
if (NULL != lpParty)
|
||||
{
|
||||
SetParty(lpParty);
|
||||
lpParty->Join(GetCID(), 0, NULL, GetMapIndex());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::SendMove
|
||||
//
|
||||
// Description : 몬스터 이동 패킷 보내기
|
||||
//
|
||||
// Inputs : None.
|
||||
//
|
||||
// Outputs : Send를 보낸 횟수.
|
||||
//
|
||||
// Returns : None.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
LONG CMonster::SendMove(unsigned short nAniNum)
|
||||
{
|
||||
// 길드 요새와 상징물은 MonMove 를 보내지 않는다. (공격시 방향 전환을 하지않도록...)
|
||||
if (Creature::CT_SIEGE_OBJECT == Creature::GetCreatureType(m_dwCID))
|
||||
{
|
||||
CSiegeObject* lpSiegeObject = reinterpret_cast<CSiegeObject* >(this);
|
||||
if (lpSiegeObject && (lpSiegeObject->IsCamp() || lpSiegeObject->IsEmblem()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
LONG nSendCount = 0;
|
||||
|
||||
if (true == GetEnchantInfo().GetFlag(Skill::SpellID::Hold) ||
|
||||
true == GetEnchantInfo().GetFlag(Skill::SpellID::Stun) ||
|
||||
true == GetEnchantInfo().GetFlag(Skill::SpellID::StoneForm))
|
||||
{
|
||||
m_MotionInfo.m_fVelocity = 0;
|
||||
}
|
||||
|
||||
if (NULL == m_CellPos.m_lpCell)
|
||||
{
|
||||
ERRLOG0(g_Log, "몬스터 이동 패킷 보내기 실패 : 몬스터가 셀을 벗어나 있습니다.");
|
||||
return nSendCount;
|
||||
}
|
||||
|
||||
PktMM pktMM;
|
||||
memset(&pktMM, 0, sizeof(PktMM));
|
||||
|
||||
pktMM.m_dwMonID = m_dwCID;
|
||||
|
||||
pktMM.m_NetworkPos = CNetworkPos(m_CurrentPos.m_fPointX, m_CurrentPos.m_fPointY, m_CurrentPos.m_fPointZ,
|
||||
m_MotionInfo.m_fDirection, (0 == m_MotionInfo.m_dwFrame) ? 0.0f : m_MotionInfo.m_fVelocity / m_MotionInfo.m_dwFrame);
|
||||
|
||||
pktMM.m_cAct = static_cast<unsigned char>(m_MotionInfo.m_wAction);
|
||||
pktMM.m_cAniNum = static_cast<unsigned char>(nAniNum);
|
||||
|
||||
// BroadCasting 한 번을 기준으로 카운트
|
||||
if (0 != m_CellPos.m_lpCell)
|
||||
{
|
||||
++nSendCount;
|
||||
m_CellPos.m_lpCell->SendAllNearCellCharacter(&pktMM, sizeof(PktMM), CmdMonMove);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRLOG4(g_Log, "CID:0x%08x 이상한 위치에 몬스터가 있습니다. (%f,%f,%f)", m_dwCID,
|
||||
m_CurrentPos.m_fPointX, m_CurrentPos.m_fPointY, m_CurrentPos.m_fPointZ);
|
||||
}
|
||||
|
||||
m_nLeaveMovingNum = nAniNum;
|
||||
|
||||
return nSendCount;
|
||||
}
|
||||
|
||||
|
||||
const int CMonster::CalculateFixLevelGap(CAggresiveCreature *pDefender)
|
||||
{
|
||||
// << 고정 레벨 갭 장치 >>
|
||||
// - 캐릭터와 몬스터의 레벨에 상관없이 스크립트에 정의된 수치만큼 몬스터의 레벨이 높은 걸로 취급한다.
|
||||
// - 보스몹 등에 이용한다.
|
||||
|
||||
if (true == m_MonsterInfo.m_bFixLevelGap)
|
||||
{
|
||||
const unsigned char cFixLevelGap = m_CreatureStatus.m_nLevel - m_MonsterInfo.m_cFixLevelGap;
|
||||
if (cFixLevelGap < pDefender->GetStatus().m_nLevel)
|
||||
{
|
||||
return m_MonsterInfo.m_cFixLevelGap;
|
||||
}
|
||||
}
|
||||
|
||||
return CAggresiveCreature::CalculateLevelGap(pDefender);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Function : CMonster::Attack
|
||||
//
|
||||
// Description : 몬스터 공격 패킷 보내기
|
||||
//
|
||||
// Inputs : pDefender - 방어자의 포인터
|
||||
//
|
||||
// Outputs : None.
|
||||
//
|
||||
// Returns : None.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bool CMonster::Attack(AtType attackType, unsigned char cDefenderNum,
|
||||
CAggresiveCreature** ppDefenders, unsigned char* cDefenderJudges, unsigned short* wDefenderMPHeal)
|
||||
{
|
||||
if (m_CreatureStatus.m_nNowHP == 0)
|
||||
{
|
||||
ERRLOG1(g_Log, "CID:0x%08x 죽은 몬스터가 공격하려고 하였습니다.", m_dwCID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cDefenderNum > AtNode::MAX_DEFENDER_NUM)
|
||||
{
|
||||
ERRLOG2(g_Log, "CID:0x%08x 몬스터가 공격할 때, 방어자의 숫자가 최대 방어자 숫자를 넘었습니다. 방어자수 : %d",
|
||||
m_dwCID, cDefenderNum);
|
||||
cDefenderNum = AtNode::MAX_DEFENDER_NUM;
|
||||
}
|
||||
|
||||
// MON_TODO : by Vincent - 2004 : 2 : 25
|
||||
DefenserNode Defenser[AtNode::MAX_DEFENDER_NUM] = {0, };
|
||||
int nDefenserCount = 0;
|
||||
// ------------
|
||||
|
||||
for (unsigned char cDefender = 0; cDefender < cDefenderNum; ++cDefender)
|
||||
{
|
||||
if (NULL == ppDefenders[cDefender]) { continue; }
|
||||
|
||||
CCharacter* lpCharacter = NULL;
|
||||
CMonster* lpSummonee = NULL;
|
||||
|
||||
// 타켓이 캐릭터인 경우
|
||||
if (Creature::CT_PC == Creature::GetCreatureType(ppDefenders[cDefender]->GetCID()))
|
||||
{
|
||||
lpCharacter = reinterpret_cast<CCharacter *>(ppDefenders[cDefender]);
|
||||
lpSummonee = lpCharacter->GetSummonee();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 타겟이 소환수인 경우
|
||||
if (Creature::IsSummonMonster(ppDefenders[cDefender]->GetCID()))
|
||||
{
|
||||
lpCharacter = reinterpret_cast<CSummonMonster *>(ppDefenders[cDefender])->GetMaster();
|
||||
}
|
||||
}
|
||||
|
||||
if (NULL == lpCharacter) { continue; }
|
||||
|
||||
unsigned char cOffencerJudge = 0;
|
||||
unsigned short wOffencerMPHeal = 0;
|
||||
unsigned short wError = PktBase::NO_SERVER_ERR;
|
||||
|
||||
const unsigned short wDamage =
|
||||
ppDefenders[cDefender]->ApplyDamage(attackType, this, cOffencerJudge, cDefenderJudges[cDefender], wOffencerMPHeal, wDefenderMPHeal[cDefender], wError);
|
||||
|
||||
if (NULL != lpSummonee)
|
||||
{
|
||||
lpSummonee->GuardMe(this, wDamage);
|
||||
}
|
||||
|
||||
lpCharacter->CalculateEquipDurability((ClientConstants::Judge_Guard == cDefenderJudges[cDefender]) ?
|
||||
AtType::GUARD : AtType::DEFENCE);
|
||||
|
||||
CGameClientDispatch* lpDispatch = lpCharacter->GetDispatcher();
|
||||
if (NULL != lpDispatch)
|
||||
{
|
||||
GameClientSendPacket::SendCharAttacked(lpDispatch->GetSendStream(), this, ppDefenders[cDefender],
|
||||
attackType, m_MotionInfo.m_fDirection, wDamage, cDefenderJudges[cDefender], wDefenderMPHeal[cDefender], wError);
|
||||
}
|
||||
|
||||
Defenser[nDefenserCount].m_cJudge = cDefenderJudges[nDefenserCount];
|
||||
Defenser[nDefenserCount].m_dwCharID = ppDefenders[nDefenserCount]->GetCID();
|
||||
Defenser[nDefenserCount].m_wMaxHP = ppDefenders[nDefenserCount]->GetStatus().m_StatusInfo.m_nMaxHP;
|
||||
Defenser[nDefenserCount].m_wMaxMP = ppDefenders[nDefenserCount]->GetStatus().m_StatusInfo.m_nMaxMP;
|
||||
Defenser[nDefenserCount].m_sCurrHP = ppDefenders[nDefenserCount]->GetStatus().m_nNowHP;
|
||||
Defenser[nDefenserCount].m_sCurrMP = ppDefenders[nDefenserCount]->GetStatus().m_nNowMP;
|
||||
Defenser[nDefenserCount].m_wMPHeal = wDefenderMPHeal[nDefenserCount];
|
||||
Defenser[nDefenserCount].m_wDamage = wDamage;
|
||||
++nDefenserCount;
|
||||
}
|
||||
|
||||
CCell* lpCell = m_CellPos.m_lpCell;
|
||||
if (NULL != lpCell)
|
||||
{
|
||||
lpCell->SendAttackInfo(GetCID(), attackType, static_cast<unsigned char>(nDefenserCount), &Defenser[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CMonster::HasSkill()
|
||||
{
|
||||
for (int i=0; i<MonsterInfo::MAX_SKILL_PATTERN; ++i)
|
||||
{
|
||||
if (0 != m_MonsterInfo.m_wSkillID[i]) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int CMonster::GetUseSkillNum() // 몇가지의 스킬을 가지고 있는가?
|
||||
{
|
||||
int count = 0;
|
||||
for (int i=0; i<MonsterInfo::MAX_SKILL_PATTERN; ++i)
|
||||
{
|
||||
if (0 != m_MonsterInfo.m_wSkillID[i]) ++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
unsigned long CMonster::GetDropRate(unsigned char cIndex)
|
||||
{
|
||||
switch (cIndex)
|
||||
{
|
||||
case MonsterInfo::LOTTERY:
|
||||
{
|
||||
if (false == CServerSetup::GetInstance().GetLotteryEvent())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MonsterInfo::F_RING:
|
||||
case MonsterInfo::D_RING:
|
||||
case MonsterInfo::C_RING:
|
||||
case MonsterInfo::B_RING:
|
||||
case MonsterInfo::A_RING:
|
||||
case MonsterInfo::F_NECKLACE:
|
||||
case MonsterInfo::D_NECKLACE:
|
||||
case MonsterInfo::C_NECKLACE:
|
||||
case MonsterInfo::B_NECKLACE:
|
||||
case MonsterInfo::A_NECKLACE:
|
||||
{
|
||||
if (false == CServerSetup::GetInstance().UseContents(GameRYL::ACCESSORY))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MonsterInfo::F_RUNE:
|
||||
case MonsterInfo::D_RUNE:
|
||||
case MonsterInfo::C_RUNE:
|
||||
case MonsterInfo::B_RUNE:
|
||||
case MonsterInfo::A_RUNE:
|
||||
case MonsterInfo::DESTRUCTION_RUNE:
|
||||
{
|
||||
if (false == CServerSetup::GetInstance().UseContents(GameRYL::RUNE))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return m_MonsterInfo.m_aryDropRate[cIndex];
|
||||
}
|
||||
|
||||
|
||||
bool CMonster::IsDeadSummonMonster(void)
|
||||
{
|
||||
if (m_bAdminCmdSummon)
|
||||
{
|
||||
return (STATE_ID_DIE == m_nCurrentState);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CMonster::LogMonsterMoveCount()
|
||||
{
|
||||
SERLOG5(g_Log, "몬스터의 이동 전송 회수 로그를 출력합니다. "
|
||||
"NormalBehaviorSendCount:%10d, AttackBehaviorSendCount:%10d, ReturnBehaviorSendCount:%10d, "
|
||||
"EscapeBehaviorSendCount:%10d, DeadBehaviorSendCount:%10d ",
|
||||
ms_NormalBehaviorSendCount, ms_AttackBehaviorSendCount, ms_ReturnBehaviorSendCount,
|
||||
ms_EscapeBehaviorSendCount, ms_DeadBehaviorSendCount);
|
||||
|
||||
ms_NormalBehaviorSendCount = ms_AttackBehaviorSendCount = ms_ReturnBehaviorSendCount =
|
||||
ms_EscapeBehaviorSendCount = ms_DeadBehaviorSendCount = 0;
|
||||
}
|
||||
|
||||
|
||||
unsigned short CMonster::ApplyDamage(AtType attackType, CAggresiveCreature* pOffencer, unsigned char &cOffencerJudge,
|
||||
unsigned char &cDefenserJudge, unsigned short& wOffencerMPHeal, unsigned short& wDefenserMPHeal,
|
||||
unsigned short &wError)
|
||||
{
|
||||
unsigned short usDamage = CAggresiveCreature::ApplyDamage(
|
||||
attackType, pOffencer, cOffencerJudge, cDefenserJudge, wOffencerMPHeal, wDefenserMPHeal, wError);
|
||||
|
||||
// 몬스터 외치기 타입 찾기
|
||||
CMonsterShout::Behavior eBehavior = CMonsterShout::NORMAL_ATTACK;
|
||||
unsigned short usShoutSkill_ID = 0;
|
||||
|
||||
if (0 == (attackType.m_wType & AtType::SKILL_BIT))
|
||||
{
|
||||
// 스킬 공격이 아니다.
|
||||
if (cDefenserJudge == ClientConstants::Judge_Critical)
|
||||
{
|
||||
eBehavior = CMonsterShout::CRITICAL_ATTACKED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
eBehavior = CMonsterShout::SKILL_ATTACKED;
|
||||
usShoutSkill_ID = attackType.m_wType;
|
||||
}
|
||||
|
||||
const char* szCharacterName = 0;
|
||||
if (0 != pOffencer && Creature::CT_PC == Creature::GetCreatureType(pOffencer->GetCID()))
|
||||
{
|
||||
szCharacterName = static_cast<CCharacter*>(pOffencer)->GetCharacterName();
|
||||
}
|
||||
|
||||
// 외치기 검색해서 외침.
|
||||
CMonsterShout::GetInstance().Shout(m_dwCID, m_MonsterInfo.m_dwKID,
|
||||
static_cast<unsigned short>(m_CurrentPos.m_fPointX),
|
||||
static_cast<unsigned short>(m_CurrentPos.m_fPointZ),
|
||||
eBehavior, szCharacterName, usShoutSkill_ID);
|
||||
|
||||
return usDamage;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
|
||||
#ifndef _CMONSTER_H_
|
||||
#define _CMONSTER_H_
|
||||
|
||||
#include <Creature/Character/Character.h>
|
||||
#include <Creature/Monster/FSM/FSM.h>
|
||||
|
||||
#include <Map/FieldMap/Cell.h>
|
||||
#include <Skill/Spell/SpellKind.h>
|
||||
#include <GameEvent/GameEventMgr.h>
|
||||
#include <Community/Party/Party.h>
|
||||
|
||||
#include "MonsterMgr.h"
|
||||
|
||||
|
||||
const unsigned long LongRange = 45; // 장거리 공격에 의해 쫓아가는 거리.
|
||||
const unsigned long OutsideSearchRange = 10; // 영역 밖에서의 시야
|
||||
const unsigned long ReturnRange = 20; // 영역
|
||||
const unsigned long MoveRange = 15; // 영역 안에서의 최대 이동거리.
|
||||
|
||||
// 전방 참조
|
||||
class CStatue;
|
||||
|
||||
class CMonster : public CAggresiveCreature
|
||||
{
|
||||
public:
|
||||
|
||||
enum MovingPattern { AREA = 0, FIXED, ROUTE, NONAREA };
|
||||
|
||||
enum _Const
|
||||
{
|
||||
MONSTER_SEARCH_RANGE = 8,
|
||||
BOSS_SEARCH_RANGE = 15,
|
||||
NAMED_SEARCH_RANGE = 15,
|
||||
CHIEF_SEARCH_RANGE = 15,
|
||||
MAGE_SEARCH_RANGE = 18,
|
||||
ACOLYTE_SEARCH_RANGE = 18,
|
||||
GUARD_SEARCH_RANGE = 15,
|
||||
|
||||
GOLD_INC_LEVEL_LIMIT = 20,
|
||||
|
||||
RUN_ANI_LIMIT_MAX = 3,
|
||||
RUN_ANI_LIMIT_MIN = 1,
|
||||
|
||||
RATE_MOVING_AND_PLAYER = 10,
|
||||
|
||||
MAX_MONSTER_UID = 0xFFFF - 0x8000 - 0x4000,
|
||||
|
||||
MONSTER_PARTY_BIT = 0x80000000
|
||||
};
|
||||
|
||||
struct MonsterCreateInfo; // 몬스터 정보 구조체
|
||||
typedef MonsterCreateInfo *LPMonsterCreateInfo;
|
||||
|
||||
CMonster();
|
||||
CMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
virtual ~CMonster();
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 초기 설정
|
||||
|
||||
bool InitMonster(Position& Pos, bool bDead = false);
|
||||
|
||||
virtual bool GetMotion(unsigned long MotionID, MotionInfo& Motion); // 몬스터 모션 정보를 얻음.
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 몬스터 행동 관련
|
||||
|
||||
bool CheckPartyTarget(); // 몬스터 파티의 타겟을 체크해서 패턴을 변경하는가?
|
||||
virtual void SearchPlayer(void); // 플레이어 검색
|
||||
|
||||
bool IsOverlap(void); // 다른 몬스터와 겹치는가 검색
|
||||
|
||||
bool Process(void); // 몬스터 행동 처리 - 내부에서 UpdateBehavior호출
|
||||
|
||||
void UpdateBehavior(unsigned long dwTick); // 몬스터 행동 업데이트
|
||||
|
||||
virtual void NormalBehavior(unsigned long dwTick); // 보통 상태
|
||||
virtual void AttackBehavior(unsigned long dwTick); // 공격 상태
|
||||
virtual void ReturnBehavior(unsigned long dwTick); // 귀환 상태
|
||||
virtual void EscapeBehavior(void); // 도망 상태
|
||||
virtual void DeadBehavior(unsigned long dwTick); // 죽은 상태
|
||||
|
||||
// Action 관련 처리
|
||||
void AttackAction();
|
||||
void WalkAttackAction(float fVelocity=0.1f);
|
||||
void RunAction(float fDistance, float fDstX, float fDstZ);
|
||||
bool IsReturn();
|
||||
|
||||
inline Position CalculateCoor(void); // 이동 후의 좌표 계산
|
||||
|
||||
virtual const int CalculateFixLevelGap(CAggresiveCreature *pDefender);
|
||||
|
||||
virtual bool Attack(AtType attackType, unsigned char cDefenderNum,
|
||||
CAggresiveCreature** ppDefenders, unsigned char* cDefenderJudges, unsigned short* wDefenderMPHeal);
|
||||
|
||||
inline virtual void Attacked(void); // 공격을 받음
|
||||
bool MultiAttack(void); // 타겟을 찾아 공격 (내부에서 Attack함수를 호출)
|
||||
void CancelTarget(bool bSaveThreat = false); // 타겟을 취소
|
||||
|
||||
virtual bool Dead(CAggresiveCreature* pOffencer); // 죽었을 때 호출되는 함수
|
||||
virtual EnemyCheck::EnemyType IsEnemy(CCreature* lpTarget, unsigned char* cResult = NULL);
|
||||
|
||||
virtual void Respawn(unsigned long dwTick);
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 파티 관련
|
||||
|
||||
void SetPID(unsigned long dwPID) { m_dwPID = dwPID; }
|
||||
unsigned long GetPID(void) { return m_dwPID; }
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 패킷을 만들어서 보내는 함수들
|
||||
|
||||
virtual LONG SendMove(unsigned short nAniNum);
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// 기타 함수
|
||||
|
||||
virtual Item::CItem* SellToCharacter(CCharacter *lpCustomer, unsigned short wKindItem, TakeType takeType,
|
||||
Item::CItem* lpRequestItem, unsigned long &dwPrice, unsigned short wCouponID, unsigned short &usError) { return NULL; }
|
||||
|
||||
bool HasSkill(void); // 스킬을 사용하는 몬스터인가?
|
||||
int GetUseSkillNum(void); // 몇가지의 스킬을 가지고 있는가?
|
||||
|
||||
inline unsigned char GetOptionBaseNum(Item::EquipType::OptionType eType);
|
||||
|
||||
unsigned long GetDropRate(unsigned char nIndex);
|
||||
unsigned long GetAwardItem(int nIndex) { return m_MonsterInfo.m_aryAwardItem[nIndex]; }
|
||||
|
||||
inline CStatue* DowncastToStatue(void);
|
||||
|
||||
// 몬스터 일련번호 얻기
|
||||
int GetSerialNumber(void) { return ((m_dwCID - Creature::MONSTER_BIT) >> 16); }
|
||||
unsigned short GetKID(void) { return static_cast<unsigned short>(m_MonsterInfo.m_dwKID); }
|
||||
|
||||
unsigned char GetPattern(void) { return m_MonsterInfo.m_cSkillPattern; }
|
||||
int GetCurrentState(void) { return m_nCurrentState; }
|
||||
short GetAttackRange(void) { return m_MonsterInfo.m_wAttackRange; }
|
||||
|
||||
bool IsDeadSummonMonster(void);
|
||||
virtual void SetGuard(bool bGuard) { return; }
|
||||
|
||||
virtual unsigned short ApplyDamage(AtType attackType, CAggresiveCreature* pOffencer, unsigned char &cOffencerJudge,
|
||||
unsigned char &cDefenserJudge, unsigned short& wOffencerMPHeal, unsigned short& wDefenserMPHeal, unsigned short &wError);
|
||||
|
||||
virtual void GuardMe(CAggresiveCreature* lpTarget, unsigned short wThreat) { return; }
|
||||
virtual void AttackCmd(CAggresiveCreature* lpTarget, unsigned short wThreat) { return; }
|
||||
|
||||
virtual bool IsPeaceMode(void) { return false; }
|
||||
virtual bool IsRideArms(void) const { return false; }
|
||||
virtual unsigned char GetNation() const { return m_MonsterInfo.m_cNation; }
|
||||
|
||||
virtual unsigned char GetRealmPoint() { return 0; }
|
||||
|
||||
static void LogMonsterMoveCount(void);
|
||||
|
||||
Broadcast2nd::CSerializeMonsterData& GetSerializeData() { return m_SerializeMonsterData; }
|
||||
|
||||
protected:
|
||||
|
||||
// 각종 상수들
|
||||
enum _Timing
|
||||
{
|
||||
FPS = 30
|
||||
};
|
||||
|
||||
MonsterInfo m_MonsterInfo;
|
||||
Position m_OriginalPosition; // 몬스터의 초기 위치
|
||||
unsigned long m_dwLastBehaviorTick; // 마지막으로 행동한 시점의 Tick
|
||||
|
||||
Broadcast2nd::CSerializeMonsterData m_SerializeMonsterData;
|
||||
|
||||
CAggresiveCreature* m_lpTarget; // 공격할 타겟
|
||||
|
||||
long m_lCurrentFrame; // 현재 진행중인 Frame을 나타냄. 동작이 끝났는지를 확인하기 위해 삽입
|
||||
|
||||
int m_nNormalMovingDelay; // 노말 상태의 이동 타이밍 간격 (주위 플레이어 수에 따라 유동적)
|
||||
int m_nLeaveMovingNum; // 남은 이동 수
|
||||
int m_nMovingPattern; // 이동 패턴 (영역, 붙박이, 루트, 비영역)
|
||||
int m_nCurrentState; // 현재 State
|
||||
|
||||
unsigned short m_wSearchRange; // 몬스터의 시야
|
||||
unsigned short m_wDefaultSearchRange; // 몬스터의 기본 시야
|
||||
|
||||
bool m_bLongRangeAttacked; // 원거리 공격? (원거리 공격에 맞으면 시야를 넓혀 타겟을 찾음)
|
||||
bool m_bScout; // 스카우터인가?
|
||||
bool m_bAttacking; // 공격 중인가?
|
||||
|
||||
unsigned short m_wRespawnArea; // 리스폰 지점에서의 랜덤영역값
|
||||
|
||||
// 옵션 요소.
|
||||
bool m_bAvoid; // 피할 턴인가? (피하기 옵션이 켜진 경우)
|
||||
bool m_bAdminCmdSummon; // Admin 명령으로 소환한 몬스터인가?
|
||||
|
||||
// 파티 관련 PID
|
||||
unsigned long m_dwPID; // 몬스터 파티 아이디
|
||||
|
||||
static LONG ms_NormalBehaviorSendCount;
|
||||
static LONG ms_AttackBehaviorSendCount;
|
||||
static LONG ms_ReturnBehaviorSendCount;
|
||||
static LONG ms_EscapeBehaviorSendCount;
|
||||
static LONG ms_DeadBehaviorSendCount;
|
||||
};
|
||||
|
||||
|
||||
struct CMonster::MonsterCreateInfo
|
||||
{
|
||||
Position m_Pos;
|
||||
|
||||
unsigned long m_dwCID;
|
||||
unsigned long m_dwPID;
|
||||
|
||||
int m_nKID;
|
||||
int m_nMovingPattern;
|
||||
|
||||
bool m_bScout;
|
||||
unsigned short m_wRespawnArea;
|
||||
|
||||
MonsterCreateInfo()
|
||||
: m_dwCID(0), m_nKID(0), m_dwPID(0), m_bScout(false), m_nMovingPattern(0), m_wRespawnArea(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Position CMonster::CalculateCoor(void)
|
||||
{
|
||||
float fVel = (true == GetEnchantInfo().GetFlag(Skill::SpellID::Slow)) ?
|
||||
m_MotionInfo.m_fVelocity / 4 : m_MotionInfo.m_fVelocity;
|
||||
|
||||
return Position(m_CurrentPos.m_fPointX + fVel * cosf(m_MotionInfo.m_fDirection),
|
||||
m_CurrentPos.m_fPointY, m_CurrentPos.m_fPointZ + fVel * sinf(m_MotionInfo.m_fDirection));
|
||||
}
|
||||
|
||||
void CMonster::Attacked(void)
|
||||
{
|
||||
m_bLongRangeAttacked = true;
|
||||
if (m_nCurrentState == STATE_ID_NORMAL || m_nCurrentState == STATE_ID_RETURN)
|
||||
{
|
||||
m_lCurrentFrame = 0;
|
||||
}
|
||||
|
||||
m_nCurrentState = CFSM::GetInstance().StateTransition(m_nCurrentState, INPUT_ID_ATTACKED_PLAYER);
|
||||
}
|
||||
|
||||
unsigned char CMonster::GetOptionBaseNum(Item::EquipType::OptionType eType)
|
||||
{
|
||||
switch (eType)
|
||||
{
|
||||
case Item::EquipType::STANDARD_OPTION: return m_MonsterInfo.m_cStandardBaseNum;
|
||||
case Item::EquipType::OVER_OPTION: return m_MonsterInfo.m_cOverBaseNum;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CStatue* CMonster::DowncastToStatue(void)
|
||||
{
|
||||
if (0 != this &&
|
||||
MonsterInfo::MIN_STATUE_KID <= m_MonsterInfo.m_dwKID &&
|
||||
MonsterInfo::MAX_STATUE_KID >= m_MonsterInfo.m_dwKID)
|
||||
{
|
||||
return reinterpret_cast<CStatue *>(this);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,460 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <Utility/Math/Math.h>
|
||||
#include <Utility/Compress/MiniLZO/MiniLZOWrapper.h>
|
||||
#include <Utility/Resource/EnsureCleanup.h>
|
||||
|
||||
#include <Network/XORCrypt/XORCrypt.h>
|
||||
|
||||
// 클라이언트에서도 쓰이므로 include를 명시한다.
|
||||
#include <Log/ServerLog.h>
|
||||
|
||||
#define ENCODEHEADER(Start_In, Length_In, PageNum_In, PageVer_In) CXORCrypt::GetInstance().EncodeHeader((Start_In), (Length_In), (PageNum_In), (PageVer_In))
|
||||
#define DECODEHEADER(Start_In, Length_In, PageNum_In, PageVer_In) CXORCrypt::GetInstance().DecodeHeader((Start_In), (Length_In), (PageNum_In), (PageVer_In))
|
||||
#define COMPRESS(In, In_len, Out, Out_len) CMiniLZO::Compress((In), (In_len), (Out), (Out_len))
|
||||
#define DECOMPRESS(In, In_len, Out, Out_len) CMiniLZO::Decompress((In), (In_len), (Out), (Out_len))
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include "MonsterMgr.h"
|
||||
#include "GMMemory.h"
|
||||
|
||||
CMonsterMgr CMonsterMgr::ms_this;
|
||||
|
||||
const char* CMonsterMgr::m_szMonsterScriptFileName = "./Script/Game/MonsterProtoType.txt";
|
||||
|
||||
|
||||
CMonsterMgr::CMonsterMgr(void)
|
||||
: m_nMonsterNum(0), m_ProtoTypeArray(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CMonsterMgr::~CMonsterMgr(void)
|
||||
{
|
||||
ClearProtoType();
|
||||
}
|
||||
|
||||
void CMonsterMgr::ClearProtoType()
|
||||
{
|
||||
if (NULL != m_ProtoTypeArray)
|
||||
{
|
||||
delete [] m_ProtoTypeArray;
|
||||
}
|
||||
}
|
||||
|
||||
bool CMonsterMgr::LoadMonstersFromFile(const char* szFileName)
|
||||
{
|
||||
int nIndex = 0;
|
||||
int nLineCount = 0;
|
||||
char strTemp[MAX_PATH];
|
||||
float fTemp = 0;
|
||||
|
||||
CDelimitedFile DelimitedFile; // 객체 소멸시, 자동 Close.
|
||||
std::vector<MonsterProtoType> monsterProtoTypeVector;
|
||||
|
||||
monsterProtoTypeVector.reserve(1000);
|
||||
MonsterProtoType tempProtoType;
|
||||
|
||||
// 매크로에 로그 코드 삽입을 잊지 말 것.
|
||||
// 매크로에서 \뒤에 공백이나 문자 삽입되지 않도록 주의할 것.
|
||||
// ( '이스케이프 시퀀스가 잘못되었습니다' 에러 발생 )
|
||||
#define READ_DATA(ColumnName, Argument) \
|
||||
if (!DelimitedFile.ReadData(Argument)) { \
|
||||
ERRLOG2(g_Log, "몬스터 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define READ_STRING(ColumnName, Buffer, BufferSize) \
|
||||
if (!DelimitedFile.ReadString(Buffer, BufferSize)) { \
|
||||
ERRLOG2(g_Log, "몬스터 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define READ_DATA_ARRAY(ColumnName, Argument, ArgumentNum) \
|
||||
for (nIndex=0; nIndex < ArgumentNum; ++nIndex) { \
|
||||
READ_DATA(ColumnName, Argument[nIndex]); \
|
||||
}
|
||||
|
||||
#define READ_SKILLID_ARRAY(ColumnName, Argument, ArgumentNum) \
|
||||
for (nIndex=0; nIndex < ArgumentNum; ++nIndex) { \
|
||||
if (!DelimitedFile.ReadString(strTemp, MAX_PATH)) { \
|
||||
ERRLOG2(g_Log, "몬스터 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
||||
return false; \
|
||||
} \
|
||||
char strString[MAX_PATH] = "0x0000"; \
|
||||
strcat(strString, (strTemp+2)); \
|
||||
Argument[nIndex] = static_cast<unsigned short>(Math::Convert::Atoi(strString)); \
|
||||
}
|
||||
|
||||
#define READ_DATA_BOOL(ColumnName, Argument) \
|
||||
if (!DelimitedFile.ReadString(strTemp, MAX_PATH)) { \
|
||||
ERRLOG2(g_Log, "몬스터 스크립트 읽기 실패 : %d행 %s컬럼에서 에러 발생!", nLineCount, #ColumnName); \
|
||||
return false; \
|
||||
} \
|
||||
Argument = (!strcmp(strTemp, "O")) ? true : false;
|
||||
|
||||
|
||||
if (!DelimitedFile.Open(szFileName ? szFileName : m_szMonsterScriptFileName))
|
||||
{
|
||||
ERRLOG1(g_Log, "%s 파일을 열 수 없습니다.", szFileName ? szFileName : m_szMonsterScriptFileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (DelimitedFile.ReadLine())
|
||||
{
|
||||
++nLineCount;
|
||||
|
||||
// 순서가 바뀌면 곤란하다니깐~!!! (버럭!)
|
||||
READ_DATA("KID", tempProtoType.m_MonsterInfo.m_dwKID);
|
||||
|
||||
READ_STRING("이름", tempProtoType.m_MonsterInfo.m_strName, MonsterInfo::MAX_NAME_LEN);
|
||||
READ_STRING("모델링 형태 플래그", tempProtoType.m_MonsterInfo.m_strModelingFlag, MonsterInfo::MAX_MODELING_FLAG_LENGTH);
|
||||
|
||||
READ_DATA_BOOL("LOD 사용 유무", tempProtoType.m_MonsterInfo.m_bUseLOD);
|
||||
|
||||
READ_DATA("국적", tempProtoType.m_MonsterInfo.m_cNation);
|
||||
|
||||
READ_STRING("스킬패턴", strTemp, MAX_PATH);
|
||||
tempProtoType.m_MonsterInfo.m_cSkillPattern = MonsterInfo::GetMonsterPattern(strTemp);
|
||||
|
||||
READ_DATA_BOOL("인챈트무시여부", tempProtoType.m_MonsterInfo.m_bIgnoreEnchant);
|
||||
|
||||
// 모션 정보
|
||||
tempProtoType.m_MonsterInfo.m_MonsterMotions[0].m_wAction = MonsterInfo::Z3D_CA_WALK;
|
||||
READ_DATA("걷기프레임", tempProtoType.m_MonsterInfo.m_MonsterMotions[0].m_dwFrame);
|
||||
|
||||
READ_DATA("걷기거리", tempProtoType.m_MonsterInfo.m_MonsterMotions[0].m_fVelocity);
|
||||
tempProtoType.m_MonsterInfo.m_MonsterMotions[0].m_fVelocity /= 100.0f; // 스크립트 값은cm 임. 미터로 변환함.
|
||||
|
||||
tempProtoType.m_MonsterInfo.m_MonsterMotions[1].m_wAction = MonsterInfo::Z3D_CA_RUN;
|
||||
READ_DATA("달리기프레임", tempProtoType.m_MonsterInfo.m_MonsterMotions[1].m_dwFrame);
|
||||
|
||||
READ_DATA("달리기거리", tempProtoType.m_MonsterInfo.m_MonsterMotions[1].m_fVelocity);
|
||||
tempProtoType.m_MonsterInfo.m_MonsterMotions[1].m_fVelocity /= 100.0f; // 스크립트 값은cm 임. 미터로 변환함.
|
||||
|
||||
tempProtoType.m_MonsterInfo.m_MonsterMotions[2].m_wAction = MonsterInfo::Z3D_CA_ATTACK;
|
||||
READ_DATA("공격프레임", tempProtoType.m_MonsterInfo.m_MonsterMotions[2].m_dwFrame);
|
||||
|
||||
tempProtoType.m_MonsterInfo.m_MonsterMotions[3].m_wAction = MonsterInfo::Z3D_CA_CASTING;
|
||||
READ_DATA("스킬프레임", tempProtoType.m_MonsterInfo.m_MonsterMotions[3].m_dwFrame);
|
||||
|
||||
READ_DATA_ARRAY("타격박스", tempProtoType.m_MonsterInfo.m_fHitBox, MonsterInfo::MAX_HITBOX_NUM);
|
||||
READ_DATA("공격거리", tempProtoType.m_MonsterInfo.m_wAttackRange);
|
||||
READ_DATA("공격각도", tempProtoType.m_MonsterInfo.m_fAttackAngle);
|
||||
|
||||
// 기본 정보
|
||||
READ_DATA("기본 경험점", tempProtoType.m_CreatureStatus.m_nExp);
|
||||
READ_DATA("레벨", tempProtoType.m_CreatureStatus.m_nLevel);
|
||||
READ_DATA("최소데미지", tempProtoType.m_CreatureStatus.m_StatusInfo.m_lMinDamage);
|
||||
READ_DATA("최대데미지", tempProtoType.m_CreatureStatus.m_StatusInfo.m_lMaxDamage);
|
||||
READ_DATA("명중", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wHitRate);
|
||||
READ_DATA("방어력", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wArmor);
|
||||
READ_DATA("회피", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wEvade);
|
||||
READ_DATA("블록", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wBlock);
|
||||
|
||||
READ_DATA("DRC", fTemp); // 기획팀의 편의를 위해 스크립트에서만 존재
|
||||
|
||||
READ_DATA("크리티컬", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wCritical);
|
||||
READ_DATA("크리티컬타입", tempProtoType.m_CreatureStatus.m_StatusInfo.m_cCriticalType);
|
||||
READ_DATA("마법력", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wMagicPower);
|
||||
READ_DATA("저항력", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wMagicResist);
|
||||
READ_DATA("속도", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wSpeed);
|
||||
READ_DATA("HP Max", tempProtoType.m_CreatureStatus.m_StatusInfo.m_nMaxHP);
|
||||
READ_DATA("MP Max", tempProtoType.m_CreatureStatus.m_StatusInfo.m_nMaxMP);
|
||||
READ_DATA("HP 회복량", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wHPRegen);
|
||||
READ_DATA("MP 회복량", tempProtoType.m_CreatureStatus.m_StatusInfo.m_wMPRegen);
|
||||
|
||||
// 기타
|
||||
READ_DATA("사이즈", tempProtoType.m_MonsterInfo.m_fSize);
|
||||
READ_DATA("스킬레벨", tempProtoType.m_MonsterInfo.m_cSkillLevel);
|
||||
|
||||
READ_DATA_BOOL("충돌타입여부", tempProtoType.m_MonsterInfo.m_bCollision);
|
||||
READ_DATA_BOOL("선제공격여부", tempProtoType.m_MonsterInfo.m_bFirstAttack);
|
||||
READ_DATA_BOOL("귀환여부", tempProtoType.m_MonsterInfo.m_bReturnPosition);
|
||||
|
||||
READ_DATA_BOOL("고정레벨갭적용여부", tempProtoType.m_MonsterInfo.m_bFixLevelGap);
|
||||
READ_DATA("고정레벨갭", tempProtoType.m_MonsterInfo.m_cFixLevelGap);
|
||||
|
||||
READ_DATA("스킬사용 확률", tempProtoType.m_MonsterInfo.m_wSkillUseRate);
|
||||
|
||||
READ_SKILLID_ARRAY("몬스터 스킬ID", tempProtoType.m_MonsterInfo.m_wSkillID, MonsterInfo::MAX_SKILL_PATTERN);
|
||||
READ_DATA("부여스킬 스펠타입", tempProtoType.m_MonsterInfo.m_cEnchantSpellType);
|
||||
READ_DATA("챈트스킬 스펠타입", tempProtoType.m_MonsterInfo.m_cChantSpellType);
|
||||
READ_DATA("스킬이펙트 사이즈", tempProtoType.m_MonsterInfo.m_fSkillEffectSize);
|
||||
|
||||
READ_DATA("리스폰타임", tempProtoType.m_MonsterInfo.m_dwRespawnTime);
|
||||
READ_DATA_ARRAY("오리지날아이템", tempProtoType.m_MonsterInfo.m_aryAwardItem, MonsterInfo::MAX_ORIGINAL_ITEM_NUM);
|
||||
READ_DATA_ARRAY("아이템드랍률", tempProtoType.m_MonsterInfo.m_aryDropRate, MonsterInfo::MAX_AWARD_KIND);
|
||||
READ_DATA("Standard-Option Item Base", tempProtoType.m_MonsterInfo.m_cStandardBaseNum);
|
||||
READ_DATA("Over-Option Item Base", tempProtoType.m_MonsterInfo.m_cOverBaseNum);
|
||||
|
||||
READ_STRING("석상 효과 타입", strTemp, MAX_PATH);
|
||||
tempProtoType.m_MonsterInfo.m_cStatueEffectType = MonsterInfo::GetStatueEffectType(strTemp);
|
||||
READ_DATA("석상 효과 퍼센트", tempProtoType.m_MonsterInfo.m_cStatueEffectPercent);
|
||||
READ_DATA_BOOL("석상 리스폰 가능 여부", tempProtoType.m_MonsterInfo.m_bStatueRespawnEnable);
|
||||
|
||||
// 공성 오브젝트 정보 읽어들이기
|
||||
READ_DATA("개발 비용", tempProtoType.m_MonsterInfo.m_dwDevelopGold);
|
||||
READ_DATA("개발 속도", tempProtoType.m_MonsterInfo.m_cDevelopSpeed);
|
||||
READ_DATA("업그레이드 비용", tempProtoType.m_MonsterInfo.m_dwUpgradeGold);
|
||||
READ_DATA("업그레이드 속도", tempProtoType.m_MonsterInfo.m_cUpgradeSpeed);
|
||||
|
||||
READ_DATA("개발 비용 절감", tempProtoType.m_MonsterInfo.m_fDevelopGoldDown);
|
||||
READ_DATA("개발 속도 향상", tempProtoType.m_MonsterInfo.m_fDevelopSpeedUp);
|
||||
READ_DATA("업그레이드 비용 절감", tempProtoType.m_MonsterInfo.m_fUpgradeGoldDown);
|
||||
READ_DATA("업그레이드 속도 향상", tempProtoType.m_MonsterInfo.m_fUpgradeSpeedUp);
|
||||
READ_DATA("방어력 향상", tempProtoType.m_MonsterInfo.m_fDefenseUp);
|
||||
READ_DATA("공경력 향상", tempProtoType.m_MonsterInfo.m_fOffenseUp);
|
||||
READ_DATA("HP 향상", tempProtoType.m_MonsterInfo.m_fHPUp);
|
||||
READ_DATA("총 수입 보너스", tempProtoType.m_MonsterInfo.m_fBonusRate);
|
||||
READ_DATA("PC 리스폰 속도 향상", tempProtoType.m_MonsterInfo.m_fRespawnSpeedUp);
|
||||
|
||||
monsterProtoTypeVector.push_back(tempProtoType);
|
||||
}
|
||||
|
||||
std::sort(monsterProtoTypeVector.begin(), monsterProtoTypeVector.end());
|
||||
|
||||
for (std::vector<MonsterProtoType>::iterator itr = monsterProtoTypeVector.begin();
|
||||
itr != monsterProtoTypeVector.end() - 1; ++itr)
|
||||
{
|
||||
if (itr->m_MonsterInfo.m_dwKID == (itr+1)->m_MonsterInfo.m_dwKID)
|
||||
{
|
||||
ERRLOG1(g_Log, "겹치는 몬스터 종류 ID가 있습니다. 종류ID:%d", itr->m_MonsterInfo.m_dwKID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_nMonsterNum = monsterProtoTypeVector.size();
|
||||
m_ProtoTypeArray = new MonsterProtoType[m_nMonsterNum];
|
||||
if (NULL == m_ProtoTypeArray)
|
||||
{
|
||||
ERRLOG0(g_Log, "몬스터 스크립트 초기화 실패 : 메모리 부족");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::copy(monsterProtoTypeVector.begin(), monsterProtoTypeVector.end(), m_ProtoTypeArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
class CFindProtoTypeFromKID : public std::unary_function<CMonsterMgr::MonsterProtoType, bool>
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CFindProtoTypeFromKID(unsigned long dwKID)
|
||||
: m_dwKID(dwKID)
|
||||
{ }
|
||||
|
||||
bool operator() (CMonsterMgr::MonsterProtoType& protoType)
|
||||
{
|
||||
return (m_dwKID == protoType.m_MonsterInfo.m_dwKID);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
const unsigned long m_dwKID;
|
||||
};
|
||||
|
||||
const CMonsterMgr::MonsterProtoType* CMonsterMgr::GetMonsterProtoType(unsigned long dwKID)
|
||||
{
|
||||
MonsterProtoType* lpFirst = m_ProtoTypeArray;
|
||||
MonsterProtoType* lpLast = m_ProtoTypeArray + m_nMonsterNum;
|
||||
MonsterProtoType* lpMid = NULL;
|
||||
|
||||
size_t nCount = m_nMonsterNum;
|
||||
size_t nCount2 = 0;
|
||||
|
||||
for (; 0 < nCount; )
|
||||
{
|
||||
nCount2 = nCount / 2;
|
||||
lpMid = lpFirst + nCount2;
|
||||
|
||||
if (lpMid->m_MonsterInfo.m_dwKID < dwKID)
|
||||
{
|
||||
lpFirst = ++lpMid, nCount -= nCount2 + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nCount = nCount2;
|
||||
}
|
||||
}
|
||||
|
||||
return (lpFirst != lpLast && !(dwKID < lpFirst->m_MonsterInfo.m_dwKID)) ? lpFirst : NULL;
|
||||
}
|
||||
|
||||
|
||||
const CMonsterMgr::MonsterProtoType* CMonsterMgr::GetMonsterProtoType(char* szName)
|
||||
{
|
||||
for (size_t nIndex = 0; nIndex < m_nMonsterNum; nIndex++)
|
||||
{
|
||||
if (0 == strncmp(szName, m_ProtoTypeArray[nIndex].m_MonsterInfo.m_strName, MonsterInfo::MAX_NAME_LEN))
|
||||
{
|
||||
return m_ProtoTypeArray + nIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool CMonsterMgr::LoadMonstersFromBinary(const char* szFileNameBinary)
|
||||
{
|
||||
HANDLE hFile = CreateFile((0 == szFileNameBinary) ? m_szMonsterScriptFileName : szFileNameBinary,
|
||||
GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) { return false; }
|
||||
|
||||
CEnsureCloseHandle readFile(hFile);
|
||||
|
||||
unsigned long dwRead = 0;
|
||||
unsigned long dwFileHighSize = 0;
|
||||
unsigned long dwFileSize = GetFileSize(hFile, &dwFileHighSize);
|
||||
|
||||
char* lpAllocated = new char[dwFileSize];
|
||||
if (NULL == lpAllocated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CEnsureDeleteArray<char> allocated(lpAllocated);
|
||||
|
||||
if (!ReadFile(hFile, lpAllocated, dwFileSize, &dwRead, NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MonsterProtoType* lpProtoType = 0;
|
||||
MonsterInfo* lpMonsterInfo = 0;
|
||||
CreatureStatus* lpCreatureStatus = 0;
|
||||
|
||||
unsigned long dwStructSize = 0;
|
||||
unsigned long dwHeaderSize = sizeof(unsigned long) + *reinterpret_cast<unsigned long*>(lpAllocated) + sizeof(unsigned long);
|
||||
unsigned long dwBufferSize = *reinterpret_cast<unsigned long*>(lpAllocated + dwHeaderSize - sizeof(unsigned long));
|
||||
|
||||
char* lpBuffer = new char[dwBufferSize];
|
||||
if (NULL == lpBuffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CEnsureDeleteArray<char> buffer(lpBuffer);
|
||||
|
||||
char* lpBufferStartPointer = lpBuffer;
|
||||
|
||||
DECOMPRESS(lpAllocated + dwHeaderSize, dwFileSize - dwHeaderSize, lpBuffer, &dwBufferSize);
|
||||
DECODEHEADER(lpBuffer, dwBufferSize, 3, 2);
|
||||
|
||||
m_nMonsterNum = dwBufferSize / (sizeof(MonsterInfo) + sizeof(CreatureStatus));
|
||||
m_ProtoTypeArray = new MonsterProtoType[m_nMonsterNum];
|
||||
if (NULL == m_ProtoTypeArray)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t nIndex = 0; nIndex < m_nMonsterNum; ++nIndex)
|
||||
{
|
||||
lpMonsterInfo = reinterpret_cast<MonsterInfo*>(lpBuffer);
|
||||
lpCreatureStatus = reinterpret_cast<CreatureStatus*>(lpBuffer + sizeof(MonsterInfo));
|
||||
|
||||
m_ProtoTypeArray[nIndex].m_MonsterInfo = MonsterInfo(*lpMonsterInfo);
|
||||
dwBufferSize -= sizeof(MonsterInfo);
|
||||
lpBuffer += sizeof(MonsterInfo);
|
||||
|
||||
m_ProtoTypeArray[nIndex].m_CreatureStatus = CreatureStatus(*lpCreatureStatus);
|
||||
dwBufferSize -= sizeof(CreatureStatus);
|
||||
lpBuffer += sizeof(CreatureStatus);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CMonsterMgr::SaveMonstersToBinary(const char* szFileNameBinary, const char* szTrashFile)
|
||||
{
|
||||
if (0 == m_ProtoTypeArray)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE hFile = CreateFile((0 == szFileNameBinary) ? m_szMonsterScriptFileName : szFileNameBinary,
|
||||
GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) { return false; }
|
||||
|
||||
CEnsureCloseHandle writeFile(hFile);
|
||||
|
||||
const size_t MAX_SCRIPT_FILE_SIZE = m_nMonsterNum * (sizeof(MonsterInfo) + sizeof(CreatureStatus));
|
||||
|
||||
char *pInputBuffer = new char[MAX_SCRIPT_FILE_SIZE];
|
||||
char *pOutputBuffer = new char[MAX_SCRIPT_FILE_SIZE];
|
||||
|
||||
CEnsureDeleteArray<char> input(pInputBuffer);
|
||||
CEnsureDeleteArray<char> output(pOutputBuffer);
|
||||
|
||||
if (0 == pInputBuffer || 0 == pOutputBuffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char *InputStartPointer = pInputBuffer;
|
||||
char *OutputStartPointer = pOutputBuffer;
|
||||
|
||||
unsigned long dwInputBufferSize = 0;
|
||||
unsigned long dwOutputBufferSize = 0;
|
||||
|
||||
for (size_t nCount = 0; nCount < m_nMonsterNum; ++nCount)
|
||||
{
|
||||
memcpy(pInputBuffer, &m_ProtoTypeArray[nCount].m_MonsterInfo, sizeof(MonsterInfo));
|
||||
dwInputBufferSize += sizeof(MonsterInfo);
|
||||
pInputBuffer += sizeof(MonsterInfo);
|
||||
|
||||
memcpy(pInputBuffer, &m_ProtoTypeArray[nCount].m_CreatureStatus, sizeof(CreatureStatus));
|
||||
dwInputBufferSize += sizeof(CreatureStatus);
|
||||
pInputBuffer += sizeof(CreatureStatus);
|
||||
}
|
||||
|
||||
ENCODEHEADER(InputStartPointer, dwInputBufferSize, 3, 2);
|
||||
COMPRESS(InputStartPointer, dwInputBufferSize, pOutputBuffer, &dwOutputBufferSize);
|
||||
|
||||
unsigned long dwWritten = 0;
|
||||
|
||||
// 쓰레기(더미) 자료
|
||||
HANDLE hTrashFile = CreateFile(szTrashFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hTrashFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ERRLOG1(g_Log, "%s 파일을 열 수 없습니다.", szTrashFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
CEnsureCloseHandle trashFile(hTrashFile);
|
||||
|
||||
unsigned long dwRead = 0;
|
||||
unsigned long dwFileHighSize = 0;
|
||||
unsigned long dwFileSize = GetFileSize(hTrashFile, &dwFileHighSize);
|
||||
|
||||
char* lpAllocated = new char[dwFileSize];
|
||||
if (NULL == lpAllocated)
|
||||
{
|
||||
ERRLOG0(g_Log, "메모리 할당에 실패하였습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
CEnsureDeleteArray<char> allocated(lpAllocated);
|
||||
|
||||
if (false == ReadFile(hTrashFile, lpAllocated, dwFileSize, &dwRead, NULL))
|
||||
{
|
||||
ERRLOG0(g_Log, "쓰레기 파일을 읽을 수 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteFile(hFile, &dwFileSize, sizeof(unsigned long), &dwWritten, 0);
|
||||
WriteFile(hFile, lpAllocated, dwFileSize, &dwWritten, 0);
|
||||
|
||||
// 올바른 자료
|
||||
WriteFile(hFile, &dwInputBufferSize, sizeof(unsigned long), &dwWritten, 0);
|
||||
WriteFile(hFile, pOutputBuffer, dwOutputBufferSize, &dwWritten, 0);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef _MONSTER_MGR_H_
|
||||
#define _MONSTER_MGR_H_
|
||||
|
||||
#pragma once
|
||||
|
||||
#define g_MonsterMgr CMonsterMgr::GetInstance()
|
||||
|
||||
#include <Pattern/Singleton.h>
|
||||
#include <Utility/DelimitedFile.h>
|
||||
#include <Creature/Character/CharacterStructure.h>
|
||||
|
||||
#include "MonsterStructure.h"
|
||||
|
||||
|
||||
class CMonsterMgr : public CSingleton<CMonsterMgr>
|
||||
{
|
||||
public:
|
||||
|
||||
struct MonsterProtoType
|
||||
{
|
||||
MonsterInfo m_MonsterInfo;
|
||||
CreatureStatus m_CreatureStatus;
|
||||
|
||||
inline bool operator < (MonsterProtoType& rhs)
|
||||
{ return m_MonsterInfo.m_dwKID < rhs.m_MonsterInfo.m_dwKID; }
|
||||
};
|
||||
|
||||
~CMonsterMgr();
|
||||
|
||||
bool LoadMonstersFromFile(const char* szFileName = 0);
|
||||
bool LoadMonstersFromBinary(const char* szFileNameBinary = 0);
|
||||
bool SaveMonstersToBinary(const char* szFileNameBinary = 0, const char* szTrashFile = 0);
|
||||
|
||||
void ClearProtoType();
|
||||
|
||||
const MonsterProtoType* GetMonsterProtoType(unsigned long dwKID);
|
||||
const MonsterProtoType* GetMonsterProtoType(char* szName);
|
||||
|
||||
size_t GetMonsterKindNum(void) { return m_nMonsterNum; }
|
||||
|
||||
private:
|
||||
|
||||
CMonsterMgr();
|
||||
|
||||
static const char* m_szMonsterScriptFileName;
|
||||
static CMonsterMgr ms_this;
|
||||
|
||||
MonsterProtoType* m_ProtoTypeArray;
|
||||
size_t m_nMonsterNum;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,319 @@
|
||||
#include "stdafx.h"
|
||||
#include "MonsterShout.h"
|
||||
#include "MonsterMgr.h"
|
||||
#include "ScriptEngine/ScriptEngine.h"
|
||||
|
||||
#include <Network/Packet/ChatPacket.h>
|
||||
#include <Network/Packet/PacketCommand.h>
|
||||
#include <Network/Dispatch/Chat/ChatDispatch.h>
|
||||
|
||||
#include <Map/FieldMap/Cell.h>
|
||||
#include <Utility/Math/Math.h>
|
||||
|
||||
#include <Creature/CreatureManager.h>
|
||||
#include <Creature/Monster/Monster.h>
|
||||
|
||||
const char* CMonsterShout::ms_DefaultFileName = "./Script/Game/MonsterChat.gsf";
|
||||
|
||||
CMonsterShout& CMonsterShout::GetInstance()
|
||||
{
|
||||
static CMonsterShout monsterShout;
|
||||
return monsterShout;
|
||||
}
|
||||
|
||||
|
||||
CMonsterShout::CMonsterShout()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
CMonsterShout::~CMonsterShout()
|
||||
{
|
||||
ShoutMap::iterator pos = m_ShoutMap.begin();
|
||||
ShoutMap::iterator end = m_ShoutMap.end();
|
||||
|
||||
for(; pos != end; ++pos)
|
||||
{
|
||||
ShoutInfo& shoutInfo = pos->second;
|
||||
|
||||
ChatNode* lpNode = shoutInfo.m_lpNextNode;
|
||||
ChatNode* lpDeleteNode = lpNode;
|
||||
|
||||
while(0 != lpNode)
|
||||
{
|
||||
lpDeleteNode = lpNode;
|
||||
lpNode = lpNode->m_lpNextNode;
|
||||
|
||||
delete lpDeleteNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SetShoutText(int nKID, int nBehavior, int nSkill_ID,
|
||||
int nChatType, int nPercentage, const char* szMessage)
|
||||
{
|
||||
CMonsterShout::ShoutInfo shoutInfo;
|
||||
CMonsterShout::ChatNode chatNode;
|
||||
memset(&shoutInfo, 0, sizeof(CMonsterShout::ShoutInfo));
|
||||
memset(&chatNode, 0, sizeof(CMonsterShout::ChatNode));
|
||||
|
||||
shoutInfo.m_nKID = nKID;
|
||||
shoutInfo.m_nBehavior = nBehavior;
|
||||
shoutInfo.m_nSkill_ID = nSkill_ID;
|
||||
shoutInfo.m_nChatType = nChatType;
|
||||
chatNode.m_nPercentage = nPercentage;
|
||||
|
||||
const CMonsterMgr::MonsterProtoType* lpMonsterProtoType =
|
||||
CMonsterMgr::GetInstance().GetMonsterProtoType(nKID);
|
||||
|
||||
if(0 != szMessage && 0 != lpMonsterProtoType)
|
||||
{
|
||||
if(shoutInfo.m_nBehavior != CMonsterShout::RESPAWN)
|
||||
{
|
||||
_snprintf(chatNode.m_szMonsterChat, MAX_PATH - 1, "%s : %s",
|
||||
lpMonsterProtoType->m_MonsterInfo.m_strName, szMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_snprintf(chatNode.m_szMonsterChat, MAX_PATH - 1, "%s", szMessage);
|
||||
}
|
||||
|
||||
chatNode.m_szMonsterChat[MAX_PATH - 1] = 0;
|
||||
|
||||
chatNode.m_usChatLength =
|
||||
static_cast<unsigned short>(strlen(chatNode.m_szMonsterChat)) + 1;
|
||||
|
||||
CMonsterShout::GetInstance().AddMonsterShout(shoutInfo, chatNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CMonsterShout::LoadScript(const char* szFileName)
|
||||
{
|
||||
SCRIPT Script = _SE_Create(szFileName);
|
||||
if (NULL == Script)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_SE_RegisterFunction(Script, SetShoutText, T_VOID,
|
||||
"MonsterChat", T_INT, T_INT, T_INT, T_INT, T_INT, T_STRING, 0);
|
||||
|
||||
_SE_Execute(Script);
|
||||
_SE_Destroy(Script);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CMonsterShout::Shout(unsigned long dwMonsterCID, unsigned long nKID,
|
||||
unsigned short usXPos, unsigned short usZPos,
|
||||
Behavior eBehavior, const char* szName, unsigned short usSkill_ID)
|
||||
{
|
||||
// KID로 검색한다.
|
||||
// 확률 및 요건을 검사한다.
|
||||
|
||||
std::pair<ShoutMap::iterator, ShoutMap::iterator> result = m_ShoutMap.equal_range(nKID);
|
||||
std::vector<int> shoutedList;
|
||||
|
||||
for(; result.first != result.second; ++result.first)
|
||||
{
|
||||
ShoutInfo& shoutInfo = result.first->second;
|
||||
bool bChatSend = false;
|
||||
|
||||
if(shoutInfo.m_nBehavior == eBehavior)
|
||||
{
|
||||
if(SKILL_ATTACK == eBehavior || SKILL_ATTACKED == eBehavior)
|
||||
{
|
||||
// 스킬에 공격당한 경우는 스킬 타입도 확인해야 한다.
|
||||
if(shoutInfo.m_nSkill_ID == 0xFFFF || shoutInfo.m_nSkill_ID == usSkill_ID)
|
||||
{
|
||||
bChatSend = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bChatSend = true;
|
||||
}
|
||||
|
||||
// 이미 외친 ChatType이면 외치지 않는다.
|
||||
if(shoutedList.end() !=
|
||||
std::find(shoutedList.begin(), shoutedList.end(), shoutInfo.m_nChatType))
|
||||
{
|
||||
bChatSend = false;
|
||||
}
|
||||
|
||||
int nRand = Math::Random::ComplexRandom(100, 1);
|
||||
|
||||
if(bChatSend && (nRand < shoutInfo.m_nTotalPercentage))
|
||||
{
|
||||
// 여러 메시지중에서 어느 메시지를 보낼지를 결정한다.
|
||||
int nPercentage = 0;
|
||||
|
||||
ChatNode* lpNode = shoutInfo.m_lpNextNode;
|
||||
while(0 != lpNode)
|
||||
{
|
||||
if(nPercentage <= nRand && nRand <= nPercentage + lpNode->m_nPercentage)
|
||||
{
|
||||
// 원하는 구간 안에 랜덤값이 있으면 Break;
|
||||
break;
|
||||
}
|
||||
|
||||
nPercentage += lpNode->m_nPercentage;
|
||||
lpNode = lpNode->m_lpNextNode;
|
||||
}
|
||||
|
||||
if(0 != lpNode)
|
||||
{
|
||||
shoutedList.push_back(shoutInfo.m_nChatType);
|
||||
|
||||
// 몬스터 채팅을 채팅서버로 보낸다. usState는 X좌표, usError는 Z좌표이다.
|
||||
// 노멀 채팅일때만 좌표가 의미가 있다.
|
||||
|
||||
char szChatBuffer[PktChat::PktChatMaxSize * 2];
|
||||
char szNameBuffer[PktChat::PktChatMaxSize];
|
||||
|
||||
szChatBuffer[0] = 0;
|
||||
szNameBuffer[0] = 0;
|
||||
|
||||
if(0 != szName)
|
||||
{
|
||||
// 이름 복사하기..
|
||||
_snprintf(szNameBuffer, PktChat::PktChatMaxSize - 1, "%s", szName);
|
||||
szNameBuffer[PktChat::PktChatMaxSize - 1];
|
||||
}
|
||||
|
||||
const char* szTarget = 0;
|
||||
char* szTargetPos = 0;
|
||||
size_t nTargetLen = 0;
|
||||
|
||||
char* szMessageResult = 0;
|
||||
size_t nMessageLen = 0;
|
||||
|
||||
switch(shoutInfo.m_nBehavior)
|
||||
{
|
||||
case NORMAL_ATTACK:
|
||||
case SKILL_ATTACK:
|
||||
case CRITICAL_ATTACK:
|
||||
|
||||
szTarget = "$DEFNAME$";
|
||||
break;
|
||||
|
||||
case NORMAL_ATTACKED:
|
||||
case SKILL_ATTACKED:
|
||||
case CRITICAL_ATTACKED:
|
||||
|
||||
szTarget = "$ATTNAME$";
|
||||
break;
|
||||
|
||||
case DEAD:
|
||||
|
||||
szTarget = "$KILLERNAME$";
|
||||
break;
|
||||
}
|
||||
|
||||
if(0 != szTarget && (szTargetPos = strstr(lpNode->m_szMonsterChat, szTarget)))
|
||||
{
|
||||
// 치환해서 문자열 복사.
|
||||
nTargetLen = strlen(szTarget);
|
||||
|
||||
const char* szLastMessage = szTargetPos + nTargetLen;
|
||||
size_t nPreMessageLen = szTargetPos - lpNode->m_szMonsterChat;
|
||||
|
||||
memcpy(szChatBuffer, lpNode->m_szMonsterChat, nPreMessageLen);
|
||||
nMessageLen = _snprintf(szChatBuffer + nPreMessageLen,
|
||||
PktChat::PktChatMaxSize * 2 - 1, "%s%s", szNameBuffer, szLastMessage);
|
||||
|
||||
szMessageResult = szChatBuffer;
|
||||
nMessageLen += nPreMessageLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
szMessageResult = lpNode->m_szMonsterChat;
|
||||
nMessageLen = lpNode->m_usChatLength;
|
||||
}
|
||||
|
||||
PktChat::PktChatCmd eChatCmd = static_cast<PktChat::PktChatCmd>(shoutInfo.m_nChatType);
|
||||
|
||||
CChatPacket chatPacket(szMessageResult, dwMonsterCID, eChatCmd, 0);
|
||||
|
||||
if(chatPacket.IsValid())
|
||||
{
|
||||
// 챗 타입은 공지/일반/외치기 뿐이다.
|
||||
|
||||
switch(eChatCmd)
|
||||
{
|
||||
case PktChat::SHOUT:
|
||||
case PktChat::NOTICE:
|
||||
|
||||
// 몬스터가 속한 존에만 전부 전송한다.
|
||||
CCreatureManager::GetInstance().SendAllCharacter(
|
||||
chatPacket.GetCompressedPacket(),
|
||||
chatPacket.GetCompressedSize(), CmdCharChat);
|
||||
break;
|
||||
|
||||
case PktChat::NORMAL:
|
||||
|
||||
{
|
||||
// 몬스터 주변 8개 셀에 전부 메시지를 보낸다.
|
||||
CMonster* lpMonster =
|
||||
CCreatureManager::GetInstance().GetMonster(dwMonsterCID);
|
||||
|
||||
CCell* lpCell = 0;
|
||||
|
||||
if(0 != lpMonster && 0 != (lpCell = lpMonster->GetCellPos().m_lpCell))
|
||||
{
|
||||
lpCell->SendAllNearCellCharacter(
|
||||
chatPacket.GetPacketData(),
|
||||
chatPacket.GetPacketSize(), CmdCharChat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void CMonsterShout::AddMonsterShout(ShoutInfo& shoutInfo, ChatNode& chatNode)
|
||||
{
|
||||
std::pair<ShoutMap::iterator, ShoutMap::iterator> result = m_ShoutMap.equal_range(shoutInfo.m_nKID);
|
||||
|
||||
for(; result.first != result.second; ++result.first)
|
||||
{
|
||||
ShoutInfo& foundShoutInfo = result.first->second;
|
||||
|
||||
if(foundShoutInfo.m_nKID == shoutInfo.m_nKID &&
|
||||
foundShoutInfo.m_nBehavior == shoutInfo.m_nBehavior &&
|
||||
foundShoutInfo.m_nSkill_ID == shoutInfo.m_nSkill_ID &&
|
||||
foundShoutInfo.m_nChatType == shoutInfo.m_nChatType)
|
||||
{
|
||||
foundShoutInfo.m_nTotalPercentage += chatNode.m_nPercentage;
|
||||
|
||||
ChatNode* lpNode = foundShoutInfo.m_lpNextNode;
|
||||
while(0 != lpNode && 0 != lpNode->m_lpNextNode)
|
||||
{
|
||||
lpNode = lpNode->m_lpNextNode;
|
||||
}
|
||||
|
||||
if(0 != lpNode && 0 == lpNode->m_lpNextNode)
|
||||
{
|
||||
lpNode->m_lpNextNode = new ChatNode(chatNode);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(result.first == result.second)
|
||||
{
|
||||
shoutInfo.m_lpNextNode = new ChatNode(chatNode);
|
||||
shoutInfo.m_nTotalPercentage = chatNode.m_nPercentage;
|
||||
m_ShoutMap.insert(std::make_pair(shoutInfo.m_nKID, shoutInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef _MONSTER_SHOUT_H_
|
||||
#define _MONSTER_SHOUT_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class CMonsterShout
|
||||
{
|
||||
public:
|
||||
|
||||
enum Behavior
|
||||
{
|
||||
NORMAL_ATTACK = 0,
|
||||
NORMAL_ATTACKED = 1,
|
||||
|
||||
SKILL_ATTACK = 2,
|
||||
SKILL_ATTACKED = 3,
|
||||
|
||||
CRITICAL_ATTACK = 4,
|
||||
CRITICAL_ATTACKED = 5,
|
||||
|
||||
RESPAWN = 6,
|
||||
DEAD = 7
|
||||
};
|
||||
|
||||
struct ChatNode
|
||||
{
|
||||
ChatNode* m_lpNextNode;
|
||||
|
||||
int m_nPercentage;
|
||||
unsigned short m_usChatLength;
|
||||
char m_szMonsterChat[MAX_PATH];
|
||||
};
|
||||
|
||||
struct ShoutInfo
|
||||
{
|
||||
int m_nKID;
|
||||
int m_nBehavior;
|
||||
int m_nSkill_ID;
|
||||
int m_nChatType;
|
||||
int m_nTotalPercentage;
|
||||
|
||||
ChatNode* m_lpNextNode;
|
||||
};
|
||||
|
||||
static CMonsterShout& GetInstance();
|
||||
|
||||
bool LoadScript(const char* szFileName = ms_DefaultFileName);
|
||||
void AddMonsterShout(ShoutInfo& shoutInfo, ChatNode& chatNode);
|
||||
|
||||
Behavior GetBehavior(unsigned short usAttackType, unsigned char cDefenseJudge);
|
||||
void Shout(unsigned long dwMonsterCID, unsigned long nKID,
|
||||
unsigned short usXPos, unsigned short usZPos,
|
||||
Behavior eBehavior, const char* szName = 0, unsigned short usSkill_ID = 0);
|
||||
|
||||
private:
|
||||
|
||||
CMonsterShout();
|
||||
~CMonsterShout();
|
||||
|
||||
static const char* ms_DefaultFileName;
|
||||
|
||||
// KID / ShoutInfo
|
||||
typedef std::multimap<int, ShoutInfo> ShoutMap;
|
||||
|
||||
ShoutMap m_ShoutMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,421 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <Creature/Character/CharacterClass.h>
|
||||
|
||||
#include "MonsterStructure.h"
|
||||
#include "GMMemory.h"
|
||||
|
||||
MonsterInfo::MonsterInfo()
|
||||
: m_dwKID(0), m_dwRespawnTime(0), m_fSize(0), m_fAttackAngle(0), m_cSkillPattern(0), m_cSkillLevel(0),
|
||||
m_cNation(Creature::STATELESS), m_cFixLevelGap(0), m_bFixLevelGap(false), m_bCollision(false),
|
||||
m_bFirstAttack(false), m_bReturnPosition(false),
|
||||
m_cEnchantSpellType(0), m_cChantSpellType(0), m_wSkillUseRate(0), m_fSkillEffectSize(1.0f),
|
||||
m_cStatueEffectType(SE_NONE), m_cStatueEffectPercent(0), m_bStatueRespawnEnable(false)
|
||||
{
|
||||
std::fill_n(m_aryAwardItem, int(MAX_ORIGINAL_ITEM_NUM), 0);
|
||||
std::fill_n(m_aryDropRate, int(MAX_AWARD_KIND), 0);
|
||||
std::fill_n(m_strName, int(MAX_NAME_LEN), 0);
|
||||
std::fill_n(m_strModelingFlag, int(MAX_MODELING_FLAG_LENGTH), 0);
|
||||
std::fill_n(m_fHitBox, int(MAX_HITBOX_NUM), 0.0f);
|
||||
std::fill_n(m_wSkillID, int(MAX_SKILL_PATTERN), 0);
|
||||
}
|
||||
|
||||
|
||||
MonsterInfo::MonsterPattern MonsterInfo::GetMonsterPattern(const char* szMonsterType)
|
||||
{
|
||||
struct TypeAndName
|
||||
{
|
||||
const char* m_szName;
|
||||
const MonsterPattern m_MonsterPattern;
|
||||
|
||||
TypeAndName(const char* szName, const MonsterPattern ePattern)
|
||||
: m_szName(szName), m_MonsterPattern(ePattern) { }
|
||||
};
|
||||
|
||||
static TypeAndName monsterTypeName[MAX_PATTERN] =
|
||||
{
|
||||
TypeAndName("Common", PATTERN_COMMON),
|
||||
TypeAndName("Warrior", PATTERN_WARRIOR),
|
||||
TypeAndName("Defender", PATTERN_DEFENDER),
|
||||
TypeAndName("Mage", PATTERN_MAGE),
|
||||
TypeAndName("Acolyte", PATTERN_ACOLYTE),
|
||||
TypeAndName("Boss", PATTERN_BOSS),
|
||||
TypeAndName("BG", PATTERN_BG),
|
||||
TypeAndName("Summon", PATTERN_SUMMON),
|
||||
TypeAndName("Structure", PATTERN_STRUCTURE),
|
||||
TypeAndName("Named", PATTERN_NAMED),
|
||||
TypeAndName("Chief", PATTERN_CHIEF),
|
||||
TypeAndName("Object", PATTERN_OBJECT),
|
||||
TypeAndName("Guard", PATTERN_GUARD),
|
||||
TypeAndName("Gather", PATTERN_GATHER)
|
||||
};
|
||||
|
||||
TypeAndName* lpTypeNamePastEnd = monsterTypeName + MAX_PATTERN;
|
||||
for (TypeAndName* lpTypeName = monsterTypeName; lpTypeName != lpTypeNamePastEnd; ++lpTypeName)
|
||||
{
|
||||
if (0 == strcmp(szMonsterType, lpTypeName->m_szName))
|
||||
{
|
||||
return lpTypeName->m_MonsterPattern;
|
||||
}
|
||||
}
|
||||
|
||||
return PATTERN_COMMON;
|
||||
}
|
||||
|
||||
MonsterInfo::StatueEffectType MonsterInfo::GetStatueEffectType(const char* szStatueEffectType)
|
||||
{
|
||||
struct TypeAndName
|
||||
{
|
||||
const char* m_szName;
|
||||
const StatueEffectType m_StatueEffectType;
|
||||
|
||||
TypeAndName(const char* szName, const StatueEffectType eEffectType)
|
||||
: m_szName(szName), m_StatueEffectType(eEffectType) { }
|
||||
};
|
||||
|
||||
static TypeAndName effectTypeName[MAX_EFFECT_TYPE_NUM] =
|
||||
{
|
||||
TypeAndName("None", SE_NONE),
|
||||
TypeAndName("HP", SE_HP),
|
||||
TypeAndName("MP", SE_MP),
|
||||
TypeAndName("EXP", SE_EXP),
|
||||
TypeAndName("DropRate", SE_DROPRATE)
|
||||
};
|
||||
|
||||
TypeAndName* lpTypeNamePastEnd = effectTypeName + MAX_EFFECT_TYPE_NUM;
|
||||
for (TypeAndName* lpTypeName = effectTypeName; lpTypeName != lpTypeNamePastEnd; ++lpTypeName)
|
||||
{
|
||||
if (0 == strcmp(szStatueEffectType, lpTypeName->m_szName))
|
||||
{
|
||||
return lpTypeName->m_StatueEffectType;
|
||||
}
|
||||
}
|
||||
|
||||
return SE_NONE;
|
||||
}
|
||||
|
||||
unsigned short MonsterInfo::GetCompleteStatueKID(unsigned short wNowKID)
|
||||
{
|
||||
switch (wNowKID)
|
||||
{
|
||||
// 1·¹º§
|
||||
case MonsterInfo::STATUE_HUMAN_LOADING1: return MonsterInfo::STATUE_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::STATUE_AKHAN_LOADING1: return MonsterInfo::STATUE_AKHAN_COMPLETE1; break;
|
||||
case MonsterInfo::BG_STATUE_HUMAN_LOADING1: return MonsterInfo::BG_STATUE_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::BG_STATUE_AKHAN_LOADING1: return MonsterInfo::BG_STATUE_AKHAN_COMPLETE1; break;
|
||||
case MonsterInfo::POWER_STATUE_HUMAN_LOADING1: return MonsterInfo::POWER_STATUE_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::POWER_STATUE_AKHAN_LOADING1: return MonsterInfo::POWER_STATUE_AKHAN_COMPLETE1; break;
|
||||
case MonsterInfo::INT_STATUE_HUMAN_LOADING1: return MonsterInfo::INT_STATUE_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::INT_STATUE_AKHAN_LOADING1: return MonsterInfo::INT_STATUE_AKHAN_COMPLETE1; break;
|
||||
case MonsterInfo::EXP_STATUE_HUMAN_LOADING1: return MonsterInfo::EXP_STATUE_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::EXP_STATUE_AKHAN_LOADING1: return MonsterInfo::EXP_STATUE_AKHAN_COMPLETE1; break;
|
||||
case MonsterInfo::WEALTH_STATUE_HUMAN_LOADING1: return MonsterInfo::WEALTH_STATUE_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::WEALTH_STATUE_AKHAN_LOADING1: return MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE1; break;
|
||||
case MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING1: return MonsterInfo::LIFE_EXTRACT_HUMAN_COMPLETE1; break;
|
||||
case MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING1: return MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE1; break;
|
||||
// 2·¹º§
|
||||
case MonsterInfo::STATUE_HUMAN_LOADING2: return MonsterInfo::STATUE_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::STATUE_AKHAN_LOADING2: return MonsterInfo::STATUE_AKHAN_COMPLETE2; break;
|
||||
case MonsterInfo::BG_STATUE_HUMAN_LOADING2: return MonsterInfo::BG_STATUE_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::BG_STATUE_AKHAN_LOADING2: return MonsterInfo::BG_STATUE_AKHAN_COMPLETE2; break;
|
||||
case MonsterInfo::POWER_STATUE_HUMAN_LOADING2: return MonsterInfo::POWER_STATUE_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::POWER_STATUE_AKHAN_LOADING2: return MonsterInfo::POWER_STATUE_AKHAN_COMPLETE2; break;
|
||||
case MonsterInfo::INT_STATUE_HUMAN_LOADING2: return MonsterInfo::INT_STATUE_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::INT_STATUE_AKHAN_LOADING2: return MonsterInfo::INT_STATUE_AKHAN_COMPLETE2; break;
|
||||
case MonsterInfo::EXP_STATUE_HUMAN_LOADING2: return MonsterInfo::EXP_STATUE_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::EXP_STATUE_AKHAN_LOADING2: return MonsterInfo::EXP_STATUE_AKHAN_COMPLETE2; break;
|
||||
case MonsterInfo::WEALTH_STATUE_HUMAN_LOADING2: return MonsterInfo::WEALTH_STATUE_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::WEALTH_STATUE_AKHAN_LOADING2: return MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE2; break;
|
||||
case MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING2: return MonsterInfo::LIFE_EXTRACT_HUMAN_COMPLETE2; break;
|
||||
case MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING2: return MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE2; break;
|
||||
}
|
||||
|
||||
return wNowKID;
|
||||
}
|
||||
|
||||
unsigned short MonsterInfo::GetLoadingStatueKID(unsigned char cRace, unsigned short wNowKID)
|
||||
{
|
||||
switch (cRace)
|
||||
{
|
||||
case CClass::HUMAN:
|
||||
{
|
||||
// 1·¹º§
|
||||
if (MonsterInfo::STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::STATUE_HUMAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::BG_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::BG_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::BG_STATUE_HUMAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::POWER_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::POWER_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::POWER_STATUE_HUMAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::INT_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::INT_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::INT_STATUE_HUMAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::EXP_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::EXP_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::EXP_STATUE_HUMAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::WEALTH_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::WEALTH_STATUE_HUMAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::LIFE_EXTRACT_NEUTRALITY1 <= wNowKID && MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING1;
|
||||
}
|
||||
// 2·¹º§
|
||||
else if (MonsterInfo::STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::STATUE_HUMAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::BG_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::BG_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::BG_STATUE_HUMAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::POWER_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::POWER_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::POWER_STATUE_HUMAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::INT_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::INT_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::INT_STATUE_HUMAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::EXP_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::EXP_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::EXP_STATUE_HUMAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::WEALTH_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::WEALTH_STATUE_HUMAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::LIFE_EXTRACT_NEUTRALITY2 <= wNowKID && MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CClass::AKHAN:
|
||||
{
|
||||
// 1·¹º§
|
||||
if (MonsterInfo::STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::STATUE_AKHAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::BG_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::BG_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::BG_STATUE_AKHAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::POWER_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::POWER_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::POWER_STATUE_AKHAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::INT_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::INT_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::INT_STATUE_AKHAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::EXP_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::EXP_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::EXP_STATUE_AKHAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::WEALTH_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::WEALTH_STATUE_AKHAN_LOADING1;
|
||||
}
|
||||
else if (MonsterInfo::LIFE_EXTRACT_NEUTRALITY1 <= wNowKID && MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING1;
|
||||
}
|
||||
// 2·¹º§
|
||||
else if (MonsterInfo::STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::STATUE_AKHAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::BG_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::BG_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::BG_STATUE_AKHAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::POWER_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::POWER_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::POWER_STATUE_AKHAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::INT_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::INT_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::INT_STATUE_AKHAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::EXP_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::EXP_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::EXP_STATUE_AKHAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::WEALTH_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::WEALTH_STATUE_AKHAN_LOADING2;
|
||||
}
|
||||
else if (MonsterInfo::LIFE_EXTRACT_NEUTRALITY2 <= wNowKID && MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return GetDefaultStatueKID(wNowKID);
|
||||
}
|
||||
|
||||
unsigned short MonsterInfo::GetDefaultStatueKID(unsigned short wNowKID)
|
||||
{
|
||||
// 1·¹º§
|
||||
if (MonsterInfo::STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::STATUE_NEUTRALITY1;
|
||||
}
|
||||
else if (MonsterInfo::BG_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::BG_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::BG_STATUE_NEUTRALITY1;
|
||||
}
|
||||
else if (MonsterInfo::POWER_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::POWER_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::POWER_STATUE_NEUTRALITY1;
|
||||
}
|
||||
else if (MonsterInfo::INT_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::INT_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::INT_STATUE_NEUTRALITY1;
|
||||
}
|
||||
else if (MonsterInfo::EXP_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::EXP_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::EXP_STATUE_NEUTRALITY1;
|
||||
}
|
||||
else if (MonsterInfo::WEALTH_STATUE_NEUTRALITY1 <= wNowKID && MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::WEALTH_STATUE_NEUTRALITY1;
|
||||
}
|
||||
else if (MonsterInfo::LIFE_EXTRACT_NEUTRALITY1 <= wNowKID && MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE1 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::LIFE_EXTRACT_NEUTRALITY1;
|
||||
}
|
||||
// 2·¹º§
|
||||
else if (MonsterInfo::STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::STATUE_NEUTRALITY2;
|
||||
}
|
||||
else if (MonsterInfo::BG_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::BG_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::BG_STATUE_NEUTRALITY2;
|
||||
}
|
||||
else if (MonsterInfo::POWER_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::POWER_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::POWER_STATUE_NEUTRALITY2;
|
||||
}
|
||||
else if (MonsterInfo::INT_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::INT_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::INT_STATUE_NEUTRALITY2;
|
||||
}
|
||||
else if (MonsterInfo::EXP_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::EXP_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::EXP_STATUE_NEUTRALITY2;
|
||||
}
|
||||
else if (MonsterInfo::WEALTH_STATUE_NEUTRALITY2 <= wNowKID && MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::WEALTH_STATUE_NEUTRALITY2;
|
||||
}
|
||||
else if (MonsterInfo::LIFE_EXTRACT_NEUTRALITY2 <= wNowKID && MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE2 >= wNowKID)
|
||||
{
|
||||
return MonsterInfo::LIFE_EXTRACT_NEUTRALITY2;
|
||||
}
|
||||
|
||||
return wNowKID;
|
||||
}
|
||||
|
||||
bool MonsterInfo::IsLoadingStatueKID(unsigned short wNowKID)
|
||||
{
|
||||
switch (wNowKID)
|
||||
{
|
||||
// 1·¹º§
|
||||
case MonsterInfo::STATUE_HUMAN_LOADING1:
|
||||
case MonsterInfo::STATUE_AKHAN_LOADING1:
|
||||
case MonsterInfo::BG_STATUE_HUMAN_LOADING1:
|
||||
case MonsterInfo::BG_STATUE_AKHAN_LOADING1:
|
||||
case MonsterInfo::POWER_STATUE_HUMAN_LOADING1:
|
||||
case MonsterInfo::POWER_STATUE_AKHAN_LOADING1:
|
||||
case MonsterInfo::INT_STATUE_HUMAN_LOADING1:
|
||||
case MonsterInfo::INT_STATUE_AKHAN_LOADING1:
|
||||
case MonsterInfo::EXP_STATUE_HUMAN_LOADING1:
|
||||
case MonsterInfo::EXP_STATUE_AKHAN_LOADING1:
|
||||
case MonsterInfo::WEALTH_STATUE_HUMAN_LOADING1:
|
||||
case MonsterInfo::WEALTH_STATUE_AKHAN_LOADING1:
|
||||
case MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING1:
|
||||
case MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING1:
|
||||
// 2·¹º§
|
||||
case MonsterInfo::STATUE_HUMAN_LOADING2:
|
||||
case MonsterInfo::STATUE_AKHAN_LOADING2:
|
||||
case MonsterInfo::BG_STATUE_HUMAN_LOADING2:
|
||||
case MonsterInfo::BG_STATUE_AKHAN_LOADING2:
|
||||
case MonsterInfo::POWER_STATUE_HUMAN_LOADING2:
|
||||
case MonsterInfo::POWER_STATUE_AKHAN_LOADING2:
|
||||
case MonsterInfo::INT_STATUE_HUMAN_LOADING2:
|
||||
case MonsterInfo::INT_STATUE_AKHAN_LOADING2:
|
||||
case MonsterInfo::EXP_STATUE_HUMAN_LOADING2:
|
||||
case MonsterInfo::EXP_STATUE_AKHAN_LOADING2:
|
||||
case MonsterInfo::WEALTH_STATUE_HUMAN_LOADING2:
|
||||
case MonsterInfo::WEALTH_STATUE_AKHAN_LOADING2:
|
||||
case MonsterInfo::LIFE_EXTRACT_HUMAN_LOADING2:
|
||||
case MonsterInfo::LIFE_EXTRACT_AKHAN_LOADING2:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MonsterInfo::IsRaceCompleteStatueKID(unsigned short wNowKID)
|
||||
{
|
||||
switch (wNowKID)
|
||||
{
|
||||
// 1·¹º§
|
||||
case MonsterInfo::STATUE_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::STATUE_AKHAN_COMPLETE1:
|
||||
case MonsterInfo::BG_STATUE_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::BG_STATUE_AKHAN_COMPLETE1:
|
||||
case MonsterInfo::POWER_STATUE_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::POWER_STATUE_AKHAN_COMPLETE1:
|
||||
case MonsterInfo::INT_STATUE_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::INT_STATUE_AKHAN_COMPLETE1:
|
||||
case MonsterInfo::EXP_STATUE_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::EXP_STATUE_AKHAN_COMPLETE1:
|
||||
case MonsterInfo::WEALTH_STATUE_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE1:
|
||||
case MonsterInfo::LIFE_EXTRACT_HUMAN_COMPLETE1:
|
||||
case MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE1:
|
||||
// 2·¹º§
|
||||
case MonsterInfo::STATUE_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::STATUE_AKHAN_COMPLETE2:
|
||||
case MonsterInfo::BG_STATUE_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::BG_STATUE_AKHAN_COMPLETE2:
|
||||
case MonsterInfo::POWER_STATUE_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::POWER_STATUE_AKHAN_COMPLETE2:
|
||||
case MonsterInfo::INT_STATUE_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::INT_STATUE_AKHAN_COMPLETE2:
|
||||
case MonsterInfo::EXP_STATUE_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::EXP_STATUE_AKHAN_COMPLETE2:
|
||||
case MonsterInfo::WEALTH_STATUE_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::WEALTH_STATUE_AKHAN_COMPLETE2:
|
||||
case MonsterInfo::LIFE_EXTRACT_HUMAN_COMPLETE2:
|
||||
case MonsterInfo::LIFE_EXTRACT_AKHAN_COMPLETE2:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
#ifndef _MONSTER_STRUCTURE_H_
|
||||
#define _MONSTER_STRUCTURE_H_
|
||||
|
||||
#include <Creature/CreatureStructure.h>
|
||||
|
||||
#pragma pack(8)
|
||||
|
||||
struct MonsterInfo
|
||||
{
|
||||
enum MaxNumber
|
||||
{
|
||||
MAX_MOTION_NUM = 4, // 최대 모션 개수
|
||||
MAX_ORIGINAL_ITEM_NUM = 5, // 몬스터 보유 잡템 최대수
|
||||
|
||||
MAX_NAME_LEN = 64, // 몬스터 이름 길이
|
||||
MAX_MODELING_FLAG_LENGTH = 32 // 모델링 데이터 이름 길이
|
||||
};
|
||||
|
||||
enum MonsterPattern
|
||||
{
|
||||
PATTERN_COMMON = 0,
|
||||
PATTERN_WARRIOR = 1, // 워리어
|
||||
PATTERN_DEFENDER = 2, // 디펜더
|
||||
PATTERN_MAGE = 3, // 메이지
|
||||
PATTERN_ACOLYTE = 4, // 어콜라이트
|
||||
PATTERN_BOSS = 5, // 보스
|
||||
PATTERN_BG = 6, // 배경(?)
|
||||
PATTERN_SUMMON = 7, // 소환수
|
||||
PATTERN_STRUCTURE = 8, // 구조물 (석상 종류)
|
||||
PATTERN_NAMED = 9, // 네임드
|
||||
PATTERN_CHIEF = 10, // 치프
|
||||
PATTERN_OBJECT = 11, // 오브젝트 (꽃, 버섯 등등..)
|
||||
PATTERN_GUARD = 12, // 경비병
|
||||
PATTERN_GATHER = 13, // 수집 타입
|
||||
MAX_PATTERN = 14
|
||||
};
|
||||
|
||||
enum SkillPattern
|
||||
{
|
||||
ATTACK_SKILL = 0, // 공격열 스킬
|
||||
RECOVER_SKILL = 1, // 회복열 스킬
|
||||
ENCHANT_SKILL = 2, // 부여열 스킬
|
||||
EXTENT_SKILL = 3, // 범위열 스킬
|
||||
BOSS_CHANT_SKILL = 4, // 보스의 챈트 스킬
|
||||
|
||||
MAX_SKILL_PATTERN = 5
|
||||
};
|
||||
|
||||
enum HitBox
|
||||
{
|
||||
X_PLUS = 0,
|
||||
X_MINUS = 1,
|
||||
Y_PLUS = 2,
|
||||
Y_MINUS = 3,
|
||||
|
||||
MAX_HITBOX_NUM = 4
|
||||
};
|
||||
|
||||
enum Z3D_CHR_ACTION
|
||||
{
|
||||
Z3D_CA_RUN = 1, // 달리기
|
||||
Z3D_CA_ATTACK = 2, // 공격
|
||||
Z3D_CA_WALK = 8, // 걷기
|
||||
Z3D_CA_CASTING = 24 // 캐스팅
|
||||
};
|
||||
|
||||
enum AwardKind
|
||||
{
|
||||
ORIGINAL1 = 0, // 잡템1
|
||||
ORIGINAL2 = 1, // 잡템2
|
||||
ORIGINAL3 = 2, // 잡템3
|
||||
ORIGINAL4 = 3, // 잡템4
|
||||
ORIGINAL5 = 4, // 잡템5
|
||||
|
||||
COIN = 5, // 돈
|
||||
GEM = 6, // 보석
|
||||
METAL = 7, // 광물
|
||||
POTION = 8, // 물약
|
||||
SKILL = 9, // 스킬북
|
||||
|
||||
CASH_ITEM = 10, // 캐쉬템
|
||||
LOTTERY = 11, // 복권
|
||||
|
||||
F_STANDARD_EQUIP = 12, // Standard-Option Item (Grade F)
|
||||
D_STANDARD_EQUIP = 13, // Standard-Option Item (Grade D)
|
||||
C_STANDARD_EQUIP = 14, // Standard-Option Item (Grade C)
|
||||
B_STANDARD_EQUIP = 15, // Standard-Option Item (Grade B)
|
||||
A_STANDARD_EQUIP = 16, // Standard-Option Item (Grade A)
|
||||
|
||||
F_OVER_EQUIP = 17, // Over-Option Item (Grade F)
|
||||
D_OVER_EQUIP = 18, // Over-Option Item (Grade D)
|
||||
C_OVER_EQUIP = 19, // Over-Option Item (Grade C)
|
||||
B_OVER_EQUIP = 20, // Over-Option Item (Grade B)
|
||||
A_OVER_EQUIP = 21, // Over-Option Item (Grade A)
|
||||
|
||||
NO_EQUIP = 22, // No-Option Item
|
||||
|
||||
//--// start..
|
||||
F_RING = 23, // 반지 (Grade F)
|
||||
D_RING = 24, // 반지 (Grade D)
|
||||
C_RING = 25, // 반지 (Grade C)
|
||||
B_RING = 26, // 반지 (Grade B)
|
||||
A_RING = 27, // 반지 (Grade A)
|
||||
|
||||
F_NECKLACE = 28, // 목걸이 (Grade F)
|
||||
D_NECKLACE = 29, // 목걸이 (Grade D)
|
||||
C_NECKLACE = 30, // 목걸이 (Grade C)
|
||||
B_NECKLACE = 31, // 목걸이 (Grade B)
|
||||
A_NECKLACE = 32, // 목걸이 (Grade A)
|
||||
|
||||
F_RUNE = 33, // 룬 (Grade F)
|
||||
D_RUNE = 34, // 룬 (Grade D)
|
||||
C_RUNE = 35, // 룬 (Grade C)
|
||||
B_RUNE = 36, // 룬 (Grade B)
|
||||
A_RUNE = 37, // 룬 (Grade A)
|
||||
DESTRUCTION_RUNE = 38, // 소멸의 룬
|
||||
|
||||
MAX_AWARD_KIND = 39
|
||||
//--// end..
|
||||
};
|
||||
|
||||
enum SpeacialKindID
|
||||
{
|
||||
// 소환수
|
||||
SUMMON_KINDLING = 3500,
|
||||
SUMMON_FLAMEWALKER = 3506,
|
||||
SUMMON_BURNINGSHIELD = 3513,
|
||||
SUMMON_FIREWING = 3520,
|
||||
SUMMON_DEATHBURN = 3527,
|
||||
|
||||
// 1레벨 석상
|
||||
// 명예의 석상
|
||||
STATUE_NEUTRALITY1 = 4001,
|
||||
STATUE_HUMAN_LOADING1 = 4002,
|
||||
STATUE_HUMAN_COMPLETE1 = 4003,
|
||||
STATUE_AKHAN_LOADING1 = 4004,
|
||||
STATUE_AKHAN_COMPLETE1 = 4005,
|
||||
|
||||
// BattleGround Server 명예의 석상
|
||||
BG_STATUE_NEUTRALITY1 = 4006,
|
||||
BG_STATUE_HUMAN_LOADING1 = 4007,
|
||||
BG_STATUE_HUMAN_COMPLETE1 = 4008,
|
||||
BG_STATUE_AKHAN_LOADING1 = 4009,
|
||||
BG_STATUE_AKHAN_COMPLETE1 = 4010,
|
||||
|
||||
// 다크 카나번 석상
|
||||
POWER_STATUE_NEUTRALITY1 = 4011, // 힘의 석상
|
||||
POWER_STATUE_HUMAN_LOADING1 = 4012,
|
||||
POWER_STATUE_HUMAN_COMPLETE1 = 4013,
|
||||
POWER_STATUE_AKHAN_LOADING1 = 4014,
|
||||
POWER_STATUE_AKHAN_COMPLETE1 = 4015,
|
||||
|
||||
INT_STATUE_NEUTRALITY1 = 4016, // 지능의 석상
|
||||
INT_STATUE_HUMAN_LOADING1 = 4017,
|
||||
INT_STATUE_HUMAN_COMPLETE1 = 4018,
|
||||
INT_STATUE_AKHAN_LOADING1 = 4019,
|
||||
INT_STATUE_AKHAN_COMPLETE1 = 4020,
|
||||
|
||||
EXP_STATUE_NEUTRALITY1 = 4021, // 경험의 석상
|
||||
EXP_STATUE_HUMAN_LOADING1 = 4022,
|
||||
EXP_STATUE_HUMAN_COMPLETE1 = 4023,
|
||||
EXP_STATUE_AKHAN_LOADING1 = 4024,
|
||||
EXP_STATUE_AKHAN_COMPLETE1 = 4025,
|
||||
|
||||
WEALTH_STATUE_NEUTRALITY1 = 4026, // 부의 석상
|
||||
WEALTH_STATUE_HUMAN_LOADING1 = 4027,
|
||||
WEALTH_STATUE_HUMAN_COMPLETE1 = 4028,
|
||||
WEALTH_STATUE_AKHAN_LOADING1 = 4029,
|
||||
WEALTH_STATUE_AKHAN_COMPLETE1 = 4030,
|
||||
|
||||
LIFE_EXTRACT_NEUTRALITY1 = 4031, // 생명 축출기
|
||||
LIFE_EXTRACT_HUMAN_LOADING1 = 4032,
|
||||
LIFE_EXTRACT_HUMAN_COMPLETE1 = 4033,
|
||||
LIFE_EXTRACT_AKHAN_LOADING1 = 4034,
|
||||
LIFE_EXTRACT_AKHAN_COMPLETE1 = 4035,
|
||||
|
||||
|
||||
// 2레벨 석상
|
||||
// 명예의 석상
|
||||
STATUE_NEUTRALITY2 = 4036,
|
||||
STATUE_HUMAN_LOADING2 = 4037,
|
||||
STATUE_HUMAN_COMPLETE2 = 4038,
|
||||
STATUE_AKHAN_LOADING2 = 4039,
|
||||
STATUE_AKHAN_COMPLETE2 = 4040,
|
||||
|
||||
// BattleGround Server 명예의 석상
|
||||
BG_STATUE_NEUTRALITY2 = 4041,
|
||||
BG_STATUE_HUMAN_LOADING2 = 4042,
|
||||
BG_STATUE_HUMAN_COMPLETE2 = 4043,
|
||||
BG_STATUE_AKHAN_LOADING2 = 4044,
|
||||
BG_STATUE_AKHAN_COMPLETE2 = 4045,
|
||||
|
||||
// 다크 카나번 석상
|
||||
POWER_STATUE_NEUTRALITY2 = 4046, // 힘의 석상
|
||||
POWER_STATUE_HUMAN_LOADING2 = 4047,
|
||||
POWER_STATUE_HUMAN_COMPLETE2 = 4048,
|
||||
POWER_STATUE_AKHAN_LOADING2 = 4049,
|
||||
POWER_STATUE_AKHAN_COMPLETE2 = 4050,
|
||||
|
||||
INT_STATUE_NEUTRALITY2 = 4051, // 지능의 석상
|
||||
INT_STATUE_HUMAN_LOADING2 = 4052,
|
||||
INT_STATUE_HUMAN_COMPLETE2 = 4053,
|
||||
INT_STATUE_AKHAN_LOADING2 = 4054,
|
||||
INT_STATUE_AKHAN_COMPLETE2 = 4055,
|
||||
|
||||
EXP_STATUE_NEUTRALITY2 = 4056, // 경험의 석상
|
||||
EXP_STATUE_HUMAN_LOADING2 = 4057,
|
||||
EXP_STATUE_HUMAN_COMPLETE2 = 4058,
|
||||
EXP_STATUE_AKHAN_LOADING2 = 4059,
|
||||
EXP_STATUE_AKHAN_COMPLETE2 = 4060,
|
||||
|
||||
WEALTH_STATUE_NEUTRALITY2 = 4061, // 부의 석상
|
||||
WEALTH_STATUE_HUMAN_LOADING2 = 4062,
|
||||
WEALTH_STATUE_HUMAN_COMPLETE2 = 4063,
|
||||
WEALTH_STATUE_AKHAN_LOADING2 = 4064,
|
||||
WEALTH_STATUE_AKHAN_COMPLETE2 = 4065,
|
||||
|
||||
LIFE_EXTRACT_NEUTRALITY2 = 4066, // 생명 축출기
|
||||
LIFE_EXTRACT_HUMAN_LOADING2 = 4067,
|
||||
LIFE_EXTRACT_HUMAN_COMPLETE2 = 4068,
|
||||
LIFE_EXTRACT_AKHAN_LOADING2 = 4069,
|
||||
LIFE_EXTRACT_AKHAN_COMPLETE2 = 4070,
|
||||
|
||||
// 석상 KID (CreatureStructure.h 에도 정의되어 있음)
|
||||
MIN_STATUE_KID = 4001,
|
||||
MAX_STATUE_KID = 4070
|
||||
};
|
||||
|
||||
enum StatueEffectType
|
||||
{
|
||||
SE_NONE = 0,
|
||||
SE_HP = 1, // Max HP 상승 효과
|
||||
SE_MP = 2, // Max MP 상승 효과
|
||||
SE_EXP = 3, // 획득 Exp 상승 효과
|
||||
SE_DROPRATE = 4, // 획득 아이템 DropRate 상승 효과
|
||||
|
||||
MAX_EFFECT_TYPE_NUM = 5
|
||||
};
|
||||
|
||||
|
||||
MotionInfo m_MonsterMotions[MAX_MOTION_NUM]; // 몬스터의 행동 정보 (걷기, 달리기, 공격, 캐스팅)
|
||||
char m_strName[MAX_NAME_LEN]; // 이름
|
||||
char m_strModelingFlag[MAX_MODELING_FLAG_LENGTH]; // 모델링 형태 플래그
|
||||
float m_fHitBox[MAX_HITBOX_NUM]; // 타격박스
|
||||
|
||||
unsigned short m_aryAwardItem[MAX_ORIGINAL_ITEM_NUM]; // 몬스터가 가지고 있는 오리지날 아이템들
|
||||
unsigned long m_aryDropRate[MAX_AWARD_KIND]; // 아이템 종류별 드랍률
|
||||
unsigned char m_cStandardBaseNum; // Standard-Option 장비의 최소 + 갯수
|
||||
unsigned char m_cOverBaseNum; // Over-Option 장비의 최소 + 갯수
|
||||
|
||||
unsigned long m_dwKID; // 종류 ID
|
||||
unsigned long m_dwRespawnTime; // 리스폰 타임
|
||||
|
||||
float m_fSize; // 사이즈
|
||||
float m_fAttackAngle; // 공격 각도
|
||||
short m_wAttackRange; // 공격 거리
|
||||
|
||||
unsigned char m_cSkillPattern; // 스킬 패턴
|
||||
unsigned char m_cSkillLevel; // 스킬 레벨
|
||||
|
||||
unsigned char m_cFixLevelGap; // 고정 레벨갭
|
||||
bool m_bFixLevelGap; // 고정 레벨갭 적용 여부
|
||||
|
||||
char m_cNation; // 국가
|
||||
|
||||
unsigned short m_wSkillUseRate; // 스킬 사용 확률
|
||||
unsigned short m_wSkillID[MAX_SKILL_PATTERN]; // 몬스터 스킬 ID
|
||||
unsigned char m_cEnchantSpellType; // 부여 스킬에 의한 스펠타입 ( Skill::SpellID::Type )
|
||||
unsigned char m_cChantSpellType; // 파티 챈트 스킬에 의한 스펠타입 ( Skill::SpellID::Type )
|
||||
float m_fSkillEffectSize; // 스킬 이펙트 사이즈 (인간: 1.0)
|
||||
|
||||
bool m_bIgnoreEnchant; // 챈트, 인챈트 무시 여부
|
||||
bool m_bCollision; // 충돌 체크 여부
|
||||
bool m_bFirstAttack; // 선제공격 여부
|
||||
bool m_bReturnPosition; // 타겟이 도망가면 제자리로 돌아가는지 여부
|
||||
bool m_bUseLOD; // LOD 처리 유무
|
||||
|
||||
// 공성 오브젝트 관련
|
||||
unsigned long m_dwDevelopGold; // 개발 비용
|
||||
unsigned char m_cDevelopSpeed; // 개발 속도 (단위 : 분)
|
||||
unsigned long m_dwUpgradeGold; // 업그레이드 비용
|
||||
unsigned char m_cUpgradeSpeed; // 업그레이드 속도 (단위 : 분)
|
||||
|
||||
float m_fDevelopGoldDown; // 개발 비용 절감 수치 (%)
|
||||
float m_fDevelopSpeedUp; // 개발 속도 향상 수치 (%)
|
||||
float m_fUpgradeGoldDown; // 업그레이드 비용 절감 수치 (%)
|
||||
float m_fUpgradeSpeedUp; // 업그레이드 속도 향상 수치 (%)
|
||||
float m_fDefenseUp; // 방어력 향상 수치 (%)
|
||||
float m_fOffenseUp; // 공격력 향상 수치 (%)
|
||||
float m_fHPUp; // HP 향상 수치 (%)
|
||||
float m_fBonusRate; // 총 수입 보너스 (%)
|
||||
float m_fRespawnSpeedUp; // PC 리스폰 속도 향상 수치 (%)
|
||||
|
||||
// 국가전 석상 효과 관련
|
||||
unsigned char m_cStatueEffectType; // 석상 효과 타입
|
||||
unsigned char m_cStatueEffectPercent; // 석상 효과 퍼센트
|
||||
bool m_bStatueRespawnEnable; // 석상 리스폰 가능 여부
|
||||
|
||||
MonsterInfo();
|
||||
static MonsterPattern GetMonsterPattern(const char* szMonsterType);
|
||||
static StatueEffectType GetStatueEffectType(const char* szStatueEffectType);
|
||||
|
||||
static unsigned short GetCompleteStatueKID(unsigned short wNowKID);
|
||||
static unsigned short GetLoadingStatueKID(unsigned char cRace, unsigned short wNowKID);
|
||||
static unsigned short GetDefaultStatueKID(unsigned short wNowKID);
|
||||
static bool IsLoadingStatueKID(unsigned short wNowKID);
|
||||
static bool IsRaceCompleteStatueKID(unsigned short wNowKID);
|
||||
};
|
||||
|
||||
|
||||
#pragma pack()
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,438 @@
|
||||
|
||||
#ifndef _PATTERN_MONSTER_H_
|
||||
#define _PATTERN_MONSTER_H_
|
||||
|
||||
#include "Monster.h"
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// 소환수
|
||||
|
||||
class CSummonMonster : public CMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CSummonMonster();
|
||||
CSummonMonster(MonsterCreateInfo& MonsterCreate, CCharacter* lpMaster);
|
||||
|
||||
virtual ~CSummonMonster();
|
||||
|
||||
void NormalBehavior(unsigned long dwTick);
|
||||
void AttackBehavior(unsigned long dwTick);
|
||||
void ReturnBehavior(unsigned long dwTick);
|
||||
void DeadBehavior(unsigned long dwTick);
|
||||
|
||||
void GuardMe(CAggresiveCreature* lpTarget, unsigned short wThreat);
|
||||
void AttackCmd(CAggresiveCreature* lpTarget, unsigned short wThreat);
|
||||
void Attacked(void);
|
||||
bool Attack(AtType attackType, unsigned char cDefenderNum,
|
||||
CAggresiveCreature** ppDefenders, unsigned char* cDefenserJudges, unsigned short* wDefenserMPHeal);
|
||||
|
||||
bool Dead(CAggresiveCreature* pOffencer);
|
||||
EnemyCheck::EnemyType IsEnemy(CCreature* lpTarget);
|
||||
bool GetMotion(unsigned long MotionID, MotionInfo &Motion);
|
||||
|
||||
void SetGuard(bool bGuard) { m_bGuard = bGuard; }
|
||||
CCharacter* GetMaster(void) { return m_lpMaster; }
|
||||
|
||||
unsigned char GetNation() const
|
||||
{
|
||||
if (NULL == m_lpMaster)
|
||||
{
|
||||
return Creature::STATELESS;
|
||||
}
|
||||
|
||||
return m_lpMaster->GetNation();
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
enum
|
||||
{
|
||||
SUPPORT_DISTANCE = 3,
|
||||
SUMMON_TIME = 4 * FPS
|
||||
};
|
||||
|
||||
CCharacter* m_lpMaster;
|
||||
|
||||
bool m_bGuard;
|
||||
bool m_bPadding[3];
|
||||
};
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// 명예의 석상
|
||||
|
||||
class CStatue : public CMonster
|
||||
{
|
||||
public:
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_HP_UPDATE_COUNT = 10, // 10번에 한번 HP 업데이트
|
||||
|
||||
STATUE_ATTACKED_TIME = 3000, // 공격 당했을때 3초당 1번씩 패킷 전송
|
||||
STATUE_LOADING_TIME = 300000, // 석상 소환 시간(5분)
|
||||
BG_STATUE_LOADING_TIME = 120000, // 배틀 그라운드 서버군 석상 소환 시간 (2분)
|
||||
UPDATE_INFO_CYCLE = 150, // 석상 정보 업데이트 주기(5초)
|
||||
|
||||
BONUS_DISTANCE = 100, // 공헌메달 보너스를 받을 수 있는 거리
|
||||
BONUS_MILEAGE = 100, // 한번에 얻는 공헌메달 보너스의 양
|
||||
BONUS_TIME = 12 // 공헌메달 보너스를 얻는 주기(1분)
|
||||
};
|
||||
|
||||
CStatue();
|
||||
CStatue(MonsterCreateInfo& MonsterCreate, CStatue* lpParent);
|
||||
|
||||
virtual ~CStatue();
|
||||
|
||||
void NormalBehavior(unsigned long dwTick);
|
||||
void AttackBehavior(unsigned long dwTick);
|
||||
void ReturnBehavior(unsigned long dwTick);
|
||||
void DeadBehavior(unsigned long dwTick);
|
||||
|
||||
void GiveMileage(char cGiveType);
|
||||
|
||||
virtual bool RegenHPAndMP(unsigned short usAdditionalHP, unsigned short usAdditionalMP, bool bAddDefaultRegenValue);
|
||||
bool Dead(CAggresiveCreature* pOffencer);
|
||||
bool Rest(void); // 배틀 그라운드에 석상이 하나도 나오지 않도록 쉬게하는 처리
|
||||
// Dead 와 동일하나 다음 석상을 소환하지 않는다.
|
||||
|
||||
EnemyCheck::EnemyType IsEnemy(CCreature* lpTarget);
|
||||
|
||||
bool CreateLinkStatue(unsigned short wKind);
|
||||
CStatue* GetLinkStatue(unsigned short wKind);
|
||||
|
||||
Item::CItem* SellToCharacter(CCharacter *lpCustomer, unsigned short wKindItem, TakeType takeType,
|
||||
Item::CItem* lpRequestItem, unsigned long &dwPrice, unsigned short wCouponID, unsigned short &usError);
|
||||
|
||||
Position& GetOriginalPos() { return m_OriginalPosition; }
|
||||
|
||||
|
||||
// 국가전 석상 인챈트 효과 종류
|
||||
unsigned char GetRealmStatueEnchantType() const { return m_MonsterInfo.m_cStatueEffectType; }
|
||||
|
||||
// 국가전 석상 인챈트 효과 퍼센트
|
||||
unsigned char GetRealmStatueEnchantPercent() const { return m_MonsterInfo.m_cStatueEffectPercent; }
|
||||
|
||||
// 국가전 석상 리스폰 가능 여부
|
||||
bool EnableRespawn() const { return m_MonsterInfo.m_bStatueRespawnEnable; }
|
||||
|
||||
// DBAgentServer 로 KID, HP 업데이트 패킷 보내는 함수
|
||||
bool SendKIDUpdate();
|
||||
bool SendHPUpdate(bool bForce=false);
|
||||
|
||||
// Client 로 Attacked 정보 보내기
|
||||
void SendAttacked();
|
||||
|
||||
protected:
|
||||
|
||||
unsigned long m_dwDuration;
|
||||
unsigned short m_wBonusTurn;
|
||||
unsigned char m_cHPUpdateCount;
|
||||
unsigned long m_dwLastAttackedTime;
|
||||
|
||||
CStatue* m_lpParent; // 석상의 초기 상태 (명예의 석상은 5가지 석상이 한 세트로 링크되어 있다.)
|
||||
CNPC* m_lpLinkNPC; // 메달 상점 역할을 위해 링크된 NPC
|
||||
};
|
||||
|
||||
class CStatueInfo
|
||||
{
|
||||
public:
|
||||
|
||||
CStatueInfo(CStatue* lpStatue, bool bBonusTurn, char cGiveType);
|
||||
bool operator () (CCharacter* lpCharacter);
|
||||
|
||||
private:
|
||||
|
||||
CStatue* m_lpStatue;
|
||||
bool m_bBonusTurn;
|
||||
char m_cGiveType;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// GM의 축복
|
||||
// edith 2010.01.16 GM의 축복의 메달 기능 구현 1분에 1~5개씩 메달 부여
|
||||
// StatueInfo 와 같은 역활을 하기 때문에 이곳에 구현함.
|
||||
class CBlessMileageInfo
|
||||
{
|
||||
public:
|
||||
|
||||
CBlessMileageInfo(char cBlessType);
|
||||
bool operator () (CCharacter* lpCharacter);
|
||||
|
||||
private:
|
||||
char m_cBlessType;
|
||||
char m_cAddMileage;
|
||||
};
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// 수집 몬스터
|
||||
|
||||
class CGatherMonster : public CMonster
|
||||
{
|
||||
public:
|
||||
CGatherMonster();
|
||||
CGatherMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CGatherMonster();
|
||||
|
||||
virtual void NormalBehavior(unsigned long dwTick);
|
||||
virtual void AttackBehavior(unsigned long dwTick);
|
||||
virtual void ReturnBehavior(unsigned long dwTick);
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// 오브젝트 몬스터 (꽃, 나무, 버섯류)
|
||||
|
||||
class CObjectMonster : public CMonster
|
||||
{
|
||||
public:
|
||||
CObjectMonster();
|
||||
CObjectMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CObjectMonster();
|
||||
|
||||
virtual void NormalBehavior(unsigned long dwTick);
|
||||
virtual void AttackBehavior(unsigned long dwTick);
|
||||
virtual void ReturnBehavior(unsigned long dwTick);
|
||||
};
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// 패턴 몬스터
|
||||
|
||||
class CSkillMonster : public CMonster
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~CSkillMonster();
|
||||
|
||||
char GetConsumeMPCount(void) { return m_cConsumeMPCount; }
|
||||
|
||||
bool Attack(AtType attackType, unsigned char cDefenderNum, CAggresiveCreature** ppDefenders, unsigned char* cDefenderJudges, unsigned short* wDefenderMPHeal);
|
||||
|
||||
virtual void NormalBehavior(unsigned long dwTick);
|
||||
virtual void AttackBehavior(unsigned long dwTick);
|
||||
|
||||
// Action 관련 처리
|
||||
void CastingAttackAction();
|
||||
void SkillAttackAction();
|
||||
|
||||
enum SkillPattern
|
||||
{
|
||||
A_SKILL = 0,
|
||||
B_SKILL = 1,
|
||||
C_SKILL = 2,
|
||||
D_SKILL = 3,
|
||||
BOSS_CHANT_SKILL = 4,
|
||||
|
||||
MAX_SKILL_PATTERN = 5
|
||||
};
|
||||
|
||||
enum EnemyFindType
|
||||
{
|
||||
TYPE_LV = 0,
|
||||
TYPE_HP = 1,
|
||||
|
||||
TYPE_HIGHEST = 0,
|
||||
TYPE_LOWEST = 1
|
||||
};
|
||||
|
||||
enum Const
|
||||
{
|
||||
USE_SKILL_MIN_LEVEL = 4,
|
||||
USE_SKILL_LEVEL = 5 // 4레벨 스킬보다 낮은 스킬을 가진 몬스터가 사용하는 고정 스킬 레벨
|
||||
// (1단계 스킬을 사용하는 몬스터는 제외)
|
||||
};
|
||||
|
||||
public:
|
||||
virtual LONG SendMove(unsigned short nAniNum);
|
||||
|
||||
protected:
|
||||
|
||||
CSkillMonster();
|
||||
CSkillMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
bool Dead(CAggresiveCreature* pOffencer); // 죽었을 때 호출되는 함수
|
||||
|
||||
// 자신의 주변에서 셀크기(32m) 반경내에 있는 아군 파티원중에 가장 레벨이 높은 몬스터를 리턴
|
||||
CAggresiveCreature* FindHighestLVMember();
|
||||
|
||||
// 해당 스킬의 범위 안에 있는 녀석들 중
|
||||
// 인챈트 부여 스킬이 걸리지 않은 가장 레벨이 높은 몬스터를 찾아서 리턴해주는 함수
|
||||
CAggresiveCreature* FindHighestLVMember(const AtType& attackType);
|
||||
|
||||
// 파티원 중에서 HP 가 fRemain 퍼센트 이하로 남아있는 파티원이 있는지 검색
|
||||
// bRegin 이 TRUE 이면 해당 공격의 범위 안에 있는 파티원 중에서 찾는다.
|
||||
CAggresiveCreature* FindLowestHPMember(const AtType& attackType, bool bRegin, float fRemainHP = 0.5f);
|
||||
|
||||
|
||||
// 패턴에 맞는 적을 찾는 함수
|
||||
// Type1 : LV, HP
|
||||
// Type2 : Highest, Lowest
|
||||
CAggresiveCreature* FindEnemy(const AtType& attackType, unsigned char cType1, unsigned char cType2);
|
||||
|
||||
|
||||
float CalculateDistance(CAggresiveCreature* pTarget);
|
||||
|
||||
virtual bool SkillAttack(void) { return false; }
|
||||
|
||||
// 일반적인 스킬 사용 함수
|
||||
bool UseSkill(AtType attackType, CAggresiveCreature** ppDefenders, char cSkillPattern);
|
||||
|
||||
// 캐스팅 액션이 끝난후 나가는 스킬 사용 함수
|
||||
bool UseCastedSkill(void);
|
||||
|
||||
unsigned long m_lastCastTime[MAX_SKILL_PATTERN]; // 마지막으로 스킬을 사용한 시간
|
||||
char m_cConsumeMPCount; // MP 소모까지 남은 카운트 수 저장
|
||||
|
||||
// Casting
|
||||
bool m_bCasting; // 마법 캐스팅 중인가?
|
||||
int m_nCastingCount; // 캐스팅 동작 횟수
|
||||
AtType m_attackType; // 캐스팅이 끝난후 나갈 스킬
|
||||
unsigned long m_dwTargetCID; // 스킬의 대상 CID (죽거나 나가버릴수 있기때문에 CID로 체크)
|
||||
char m_cSkillPattern; // m_lastCastTime 을 갱신하기 위해 사용한 스킬의 type index (m_skillID 의 Index 이다.)
|
||||
};
|
||||
|
||||
|
||||
class CDefenderMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CDefenderMonster();
|
||||
CDefenderMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CDefenderMonster();
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
|
||||
class CWarriorMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CWarriorMonster();
|
||||
CWarriorMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CWarriorMonster();
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
|
||||
class CAcolyteMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
enum Distance
|
||||
{
|
||||
NEAR_DIST_TO_TARGET = 10,
|
||||
FAR_DIST_TO_TARGET = 15
|
||||
};
|
||||
|
||||
CAcolyteMonster();
|
||||
CAcolyteMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CAcolyteMonster();
|
||||
|
||||
void AttackBehavior(unsigned long dwTick);
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
|
||||
class CMageMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
enum Distance
|
||||
{
|
||||
NEAR_DIST_TO_TARGET = 10,
|
||||
FAR_DIST_TO_TARGET = 15
|
||||
};
|
||||
|
||||
CMageMonster();
|
||||
CMageMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CMageMonster();
|
||||
|
||||
void AttackBehavior(unsigned long dwTick);
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
class CBossMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CBossMonster();
|
||||
CBossMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CBossMonster();
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
class CChiefMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CChiefMonster();
|
||||
CChiefMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CChiefMonster();
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
class CNamedMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CNamedMonster();
|
||||
CNamedMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CNamedMonster();
|
||||
|
||||
void Respawn(unsigned long dwTick);
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
|
||||
Position m_RespawnPosition; // 리스폰 중심 좌표
|
||||
};
|
||||
|
||||
class CGuardMonster : public CSkillMonster
|
||||
{
|
||||
public:
|
||||
|
||||
CGuardMonster();
|
||||
CGuardMonster(MonsterCreateInfo& MonsterCreate, bool bAdminCmdSummon = false);
|
||||
|
||||
virtual ~CGuardMonster();
|
||||
|
||||
void SearchPlayer();
|
||||
|
||||
protected:
|
||||
|
||||
bool SkillAttack(void);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,351 @@
|
||||
#include "stdafx.h"
|
||||
#include "VirtualMonsterMgr.h"
|
||||
|
||||
#include <Creature/Creature.h>
|
||||
#include <Creature/Monster/Monster.h>
|
||||
#include <Creature/Monster/PatternMonster.h>
|
||||
|
||||
#include <Network/Dispatch/GameClient/SendCharAttack.h>
|
||||
|
||||
|
||||
|
||||
CVirtualMonsterMgr::CVirtualMonsterMgr()
|
||||
: m_usSummonCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
CVirtualMonsterMgr::~CVirtualMonsterMgr()
|
||||
{
|
||||
DestroyMonsterList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct FnDeleteSecond
|
||||
{
|
||||
template<typename PairType>
|
||||
bool operator() (PairType& pair) { if(NULL != pair.second) { delete pair.second; } return true; }
|
||||
};
|
||||
|
||||
struct FnLeaveParty
|
||||
{
|
||||
template<typename PairType>
|
||||
bool operator() (PairType& pair)
|
||||
{
|
||||
if(NULL != pair.second)
|
||||
{
|
||||
CParty* lpParty = (pair.second)->GetParty();
|
||||
if (lpParty)
|
||||
{
|
||||
lpParty->Leave((pair.second)->GetCID(), 0, (pair.second)->GetMapIndex());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void CVirtualMonsterMgr::DestroyMonsterList()
|
||||
{
|
||||
std::for_each(m_MonsterMap.begin(), m_MonsterMap.end(), FnLeaveParty());
|
||||
std::for_each(m_MonsterMap.begin(), m_MonsterMap.end(), FnDeleteSecond());
|
||||
m_MonsterMap.clear();
|
||||
m_AdminMonsterUIDMap.clear();
|
||||
}
|
||||
|
||||
bool CVirtualMonsterMgr::AddMonster(CMonster* lpMonster)
|
||||
{
|
||||
if (NULL == lpMonster) return false;
|
||||
|
||||
unsigned long dwCID = lpMonster->GetCID();
|
||||
bool bResult = false;
|
||||
|
||||
Creature::CreatureType eCreatureType = Creature::GetCreatureType(dwCID);
|
||||
|
||||
if (Creature::CT_MONSTER == eCreatureType ||
|
||||
Creature::CT_SUMMON == eCreatureType ||
|
||||
Creature::CT_STRUCT == eCreatureType)
|
||||
{
|
||||
bResult = m_MonsterMap.insert(std::make_pair(dwCID, reinterpret_cast<CMonster *>(lpMonster))).second;
|
||||
|
||||
unsigned short wKindID = static_cast<unsigned short>( (dwCID & Creature::MONSTER_KIND_BIT) );
|
||||
if ( bResult && m_AdminMonsterUIDMap.end() == m_AdminMonsterUIDMap.find(wKindID) )
|
||||
{
|
||||
m_AdminMonsterUIDMap.insert(std::make_pair(wKindID, INIT_UID)).second;
|
||||
}
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
CCreature* CVirtualMonsterMgr::GetCreature(unsigned long dwCID)
|
||||
{
|
||||
Creature::CreatureType eCreatureType = Creature::GetCreatureType(dwCID);
|
||||
|
||||
if (Creature::CT_MONSTER == eCreatureType || Creature::CT_SUMMON == eCreatureType || Creature::CT_STRUCT == eCreatureType)
|
||||
return (CCreature *)GetMonster(dwCID);
|
||||
|
||||
return (CCreature *)NULL;
|
||||
}
|
||||
|
||||
CAggresiveCreature* CVirtualMonsterMgr::GetAggresiveCreature(unsigned long dwCID)
|
||||
{
|
||||
Creature::CreatureType eCreatureType = Creature::GetCreatureType(dwCID);
|
||||
|
||||
if (Creature::CT_MONSTER == eCreatureType || Creature::CT_SUMMON == eCreatureType || Creature::CT_STRUCT == eCreatureType)
|
||||
return (CAggresiveCreature *)GetMonster(dwCID);
|
||||
|
||||
return (CAggresiveCreature *)NULL;
|
||||
}
|
||||
|
||||
CMonster* CVirtualMonsterMgr::GetMonster(unsigned long dwCID)
|
||||
{
|
||||
MonsterMap::iterator pos = m_MonsterMap.find(dwCID);
|
||||
return (pos != m_MonsterMap.end()) ? pos->second : NULL;
|
||||
}
|
||||
|
||||
|
||||
void CVirtualMonsterMgr::ProcessAllMonster()
|
||||
{
|
||||
MonsterMap::iterator pos = m_MonsterMap.begin();
|
||||
MonsterMap::iterator end = m_MonsterMap.end();
|
||||
|
||||
for (; pos != end; )
|
||||
{
|
||||
CMonster* lpMonster = pos->second;
|
||||
|
||||
if (lpMonster)
|
||||
{
|
||||
lpMonster->Process();
|
||||
}
|
||||
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
void CVirtualMonsterMgr::ProcessMonsterRegenHPAndMP()
|
||||
{
|
||||
MonsterMap::iterator pos = m_MonsterMap.begin();
|
||||
MonsterMap::iterator end = m_MonsterMap.end();
|
||||
|
||||
for (; pos != end; )
|
||||
{
|
||||
CMonster* lpMonster = pos->second;
|
||||
|
||||
if (lpMonster)
|
||||
{
|
||||
lpMonster->RegenHPAndMP(0, 0, true);
|
||||
}
|
||||
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
void CVirtualMonsterMgr::ProcessSummonMonsterDead(void)
|
||||
{
|
||||
MonsterMap::iterator pos = m_MonsterMap.begin();
|
||||
MonsterMap::iterator end = m_MonsterMap.end();
|
||||
|
||||
for (; pos != end; )
|
||||
{
|
||||
CMonster* lpMonster = pos->second;
|
||||
|
||||
if (lpMonster && true == lpMonster->IsDeadSummonMonster())
|
||||
{
|
||||
pos = m_MonsterMap.erase(pos);
|
||||
delete lpMonster;
|
||||
}
|
||||
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CVirtualMonsterMgr::IsSummonee(unsigned long dwCID)
|
||||
{
|
||||
return Creature::IsSummonMonster(dwCID);
|
||||
}
|
||||
|
||||
|
||||
bool CVirtualMonsterMgr::SummonMonster(int nKID, Position Pos, CCharacter* lpMaster)
|
||||
{
|
||||
CMonster::MonsterCreateInfo tempInfo;
|
||||
|
||||
tempInfo.m_dwCID = Creature::SUMMON_MONSTER_BIT + (m_usSummonCount << 16) + nKID;
|
||||
if (0 != GetMonster(tempInfo.m_dwCID))
|
||||
{
|
||||
ERRLOG0(g_Log, "몬스터 소환에 실패하였습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
tempInfo.m_nKID = nKID;
|
||||
tempInfo.m_Pos = Pos;
|
||||
|
||||
CMonster* lpSummonMonster = 0;
|
||||
if (0 != lpMaster)
|
||||
{
|
||||
CAggresiveCreature* lpSummonee = lpMaster->GetSummonee();
|
||||
if (0 != lpSummonee)
|
||||
{
|
||||
lpSummonee->Dead(0);
|
||||
}
|
||||
}
|
||||
|
||||
lpSummonMonster = new CSummonMonster(tempInfo, lpMaster);
|
||||
if (0 == lpSummonMonster)
|
||||
{
|
||||
ERRLOG0(g_Log, "몬스터 소환에 실패하였습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
lpSummonMonster->SetMapIndex(lpMaster->GetMapIndex());
|
||||
if (false == lpSummonMonster->InitMonster(tempInfo.m_Pos))
|
||||
{
|
||||
ERRLOG0(g_Log, "소환 몬스터 초기화에 실패하였습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
AddMonster(lpSummonMonster);
|
||||
|
||||
m_usSummonCount++;
|
||||
if (CMonster::MAX_MONSTER_UID == m_usSummonCount) { m_usSummonCount = 0; }
|
||||
|
||||
if (0 != lpMaster)
|
||||
{
|
||||
lpMaster->SetSummonee(lpSummonMonster);
|
||||
|
||||
GameClientSendPacket::SendCharSummon(lpMaster->GetCID(), lpSummonMonster);
|
||||
}
|
||||
else
|
||||
{
|
||||
lpSummonMonster->SendMove(CMonster::RUN_ANI_LIMIT_MIN);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CVirtualMonsterMgr::AdminSummonMonster(int nKID, Position Pos, unsigned short wMapIndex)
|
||||
{
|
||||
CMonster::MonsterCreateInfo tempInfo;
|
||||
CMonster* lpNewMonster = 0;
|
||||
|
||||
// nKID 가 정상인지 체크
|
||||
const CMonsterMgr::MonsterProtoType* lpProtoType = CMonsterMgr::GetInstance().GetMonsterProtoType(nKID);
|
||||
if (0 == lpProtoType)
|
||||
{
|
||||
ERRLOG1(g_Log, "KID : %d 몬스터를 소환하는데 실패하였습니다.", nKID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 존당 최대 몬스터 수를 초과하는지 체크
|
||||
if (GetMonsterNum() >= CMonster::MAX_MONSTER_UID)
|
||||
{
|
||||
ERRLOG0(g_Log, "현재 존에 몬스터 수가 최대입니다. 더이상 몬스터를 생성할 수 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 몬스터 맵에서 적당한 숫자를 넘겨준다. (CID 를 유니크하게 하기 위해서)
|
||||
unsigned long dwUID = GetAvailableMonsterUID(static_cast<unsigned short>(nKID));
|
||||
if (dwUID == NO_BLANK_UID)
|
||||
{
|
||||
ERRLOG1(g_Log, "KindID:%d 해당 종류의 몬스터 수가 최대입니다. 더이상 몬스터를 생성할 수 없습니다.", nKID);
|
||||
return false;
|
||||
}
|
||||
|
||||
tempInfo.m_dwCID = (dwUID << 16) + nKID;
|
||||
tempInfo.m_nKID = nKID;
|
||||
tempInfo.m_dwPID = 0;
|
||||
tempInfo.m_Pos = Pos;
|
||||
tempInfo.m_bScout = false;
|
||||
tempInfo.m_nMovingPattern = 0;
|
||||
tempInfo.m_wRespawnArea = CCell::CELL_DISTANCE;
|
||||
|
||||
// MON_TODO : 몬스터 타입에 맞게 생성
|
||||
switch (lpProtoType->m_MonsterInfo.m_cSkillPattern)
|
||||
{
|
||||
case MonsterInfo::PATTERN_DEFENDER:
|
||||
lpNewMonster = new CDefenderMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_WARRIOR:
|
||||
lpNewMonster = new CWarriorMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_ACOLYTE:
|
||||
lpNewMonster = new CAcolyteMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_MAGE:
|
||||
lpNewMonster = new CMageMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_BOSS:
|
||||
lpNewMonster = new CBossMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_NAMED:
|
||||
lpNewMonster = new CNamedMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_CHIEF:
|
||||
lpNewMonster = new CChiefMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_OBJECT:
|
||||
lpNewMonster = new CObjectMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_GUARD:
|
||||
lpNewMonster = new CGuardMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
case MonsterInfo::PATTERN_GATHER:
|
||||
lpNewMonster = new CGatherMonster(tempInfo, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
lpNewMonster = new CMonster(tempInfo, true);
|
||||
break;
|
||||
};
|
||||
|
||||
if (0 == lpNewMonster)
|
||||
{
|
||||
ERRLOG0(g_Log, "몬스터 생성에 실패하였습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
lpNewMonster->SetMapIndex(wMapIndex);
|
||||
if (false == lpNewMonster->InitMonster(tempInfo.m_Pos))
|
||||
{
|
||||
ERRLOG0(g_Log, "몬스터 초기화에 실패하였습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return AddMonster(lpNewMonster);
|
||||
}
|
||||
|
||||
|
||||
unsigned short CVirtualMonsterMgr::GetAvailableMonsterUID(unsigned short wKindID)
|
||||
{
|
||||
if (m_AdminMonsterUIDMap.end() == m_AdminMonsterUIDMap.find(wKindID))
|
||||
{
|
||||
m_AdminMonsterUIDMap.insert(std::make_pair(wKindID, INIT_UID)).second;
|
||||
}
|
||||
|
||||
unsigned long nUID = ((m_AdminMonsterUIDMap[wKindID] << 16) | wKindID);
|
||||
if (NULL != GetMonster(nUID))
|
||||
{
|
||||
if (m_AdminMonsterUIDMap[wKindID] == INIT_UID)
|
||||
{
|
||||
// 여유 공간이 없다면...
|
||||
return NO_BLANK_UID;
|
||||
}
|
||||
|
||||
m_AdminMonsterUIDMap[wKindID] = INIT_UID - 1;
|
||||
return INIT_UID;
|
||||
}
|
||||
|
||||
return m_AdminMonsterUIDMap[wKindID]--;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef _VIRTUAL_AREA_MONSTER_MANAGER_H_
|
||||
#define _VIRTUAL_AREA_MONSTER_MANAGER_H_
|
||||
|
||||
#pragma once
|
||||
#pragma warning(disable:4800)
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <boost/pool/pool.hpp>
|
||||
#include <boost/pool/pool_alloc.hpp>
|
||||
|
||||
|
||||
// 전방 참조
|
||||
class CCreature;
|
||||
class CAggresiveCreature;
|
||||
class CCharacter;
|
||||
class CMonster;
|
||||
struct Position;
|
||||
|
||||
|
||||
// Singleton 아님!!
|
||||
class CVirtualMonsterMgr
|
||||
{
|
||||
public:
|
||||
CVirtualMonsterMgr();
|
||||
~CVirtualMonsterMgr();
|
||||
|
||||
enum eAdminMonster
|
||||
{
|
||||
NO_BLANK_UID = 0x8FFF,
|
||||
INIT_UID = 0x8FFE
|
||||
};
|
||||
|
||||
bool AddMonster(CMonster* lpMonster);
|
||||
|
||||
CCreature* GetCreature(unsigned long dwCID);
|
||||
CAggresiveCreature* GetAggresiveCreature(unsigned long dwCID);
|
||||
CMonster* GetMonster(unsigned long dwCID);
|
||||
|
||||
bool IsSummonee(unsigned long dwCID);
|
||||
bool SummonMonster(int nKID, Position Pos, CCharacter* lpMaster);
|
||||
bool AdminSummonMonster(int nKID, Position Pos, unsigned short wMapIndex);
|
||||
|
||||
void ProcessAllMonster();
|
||||
void ProcessMonsterRegenHPAndMP();
|
||||
void ProcessSummonMonsterDead(void);
|
||||
|
||||
unsigned short GetAvailableMonsterUID(unsigned short wKindID);
|
||||
unsigned short GetMonsterNum(void) { return static_cast<unsigned short>(m_MonsterMap.size()); }
|
||||
|
||||
// Type 정의
|
||||
typedef std::map<unsigned long, CMonster*, std::less<unsigned long>,
|
||||
boost::fast_pool_allocator<std::pair<unsigned long, CMonster*> > > MonsterMap;
|
||||
|
||||
typedef std::map<unsigned short, unsigned short> AdminMonsterUIDMap; // < KindID, NextUID(상위16비트) >
|
||||
|
||||
MonsterMap& GetMonsterMap() { return m_MonsterMap; }
|
||||
|
||||
private:
|
||||
void DestroyMonsterList();
|
||||
|
||||
MonsterMap m_MonsterMap;
|
||||
AdminMonsterUIDMap m_AdminMonsterUIDMap;
|
||||
|
||||
// 룬 오프 소환 몬스터 카운터 수
|
||||
unsigned short m_usSummonCount;
|
||||
};
|
||||
|
||||
|
||||
#endif // _VIRTUAL_AREA_MONSTER_MANAGER_H_
|
||||
Reference in New Issue
Block a user