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:
2025-11-29 20:17:20 +09:00
parent 5d3cd64a25
commit dd97ddec92
11602 changed files with 1446576 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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_)

View File

@@ -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;
}

View File

@@ -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_)

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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]--;
}

View File

@@ -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_