934 lines
31 KiB
C++
934 lines
31 KiB
C++
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#include <windows.h>
|
||
|
||
#include <cmath>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include <toolkit/XEnv.h>
|
||
#include <toolkit/ILock.h>
|
||
#include <logging/FileLog.h>
|
||
|
||
|
||
#include "GameRule.h"
|
||
#include "StructMisc.h"
|
||
#include "Constant.h"
|
||
#include "StructCreature.h"
|
||
|
||
|
||
namespace GameRule
|
||
{
|
||
|
||
bool bDirectInventoryLoot = false; // Whether drop should go directly in inventory instead of dropping on the ground
|
||
|
||
|
||
|
||
|
||
bool bItemDurabilitySwitch = false; // Credits to AziaMafia: Durability consumption switch
|
||
float fPlayerKillerBonusRate = 0.1f; // Credits to AziaMafia: Some stupid bullshit TODO
|
||
|
||
|
||
AR_TIME nBattleArenaReconnectWaitDuration = 18000; // Time to wait for a disconnected user to rejoin the match (default: 3 minutes)
|
||
|
||
float fPlyMod = 0.0f;
|
||
float fPartyEXPRate[ 7 ] = { 1, 1, 1, 1, 1, 1, 1 }; // 파티 경치 보너스int
|
||
int nPartyExpPenaltyLevel = 5;
|
||
float fEXPRate = 1.0f; // 경치 비율
|
||
|
||
float fGoldDropRate = 1.0f; // 돈 드랍 비율
|
||
float fCardDropRate = 1.0f;
|
||
float fItemDropRate = 1.0f; // 아이템 드랍 비율
|
||
float fChaosDropRate = 1.0f; // 혼돈 드랍 비율
|
||
float fPVPDamageRateForPlayer = 0.05f; // Damage ratio applied in PvP (attacker: player)
|
||
float fPVPDamageRateForSummon = 0.05f; // Damage ratio applied in PvP (attacker: summon)
|
||
float fEVPDamageRate = 1.0f;
|
||
float fEVSDamageRate = 1.0f;
|
||
float fEVPBossDamageRate = 1.0f;
|
||
float fEVSBossDamageRate = 1.0f;
|
||
|
||
float fStaminaBonusRate = 1.0f; // Stamina bonus benefit rate (EXP/JP)
|
||
float fForgottenStaminaBonusRate = 1.0f;
|
||
float fStaminaRegenRate = 2.5f;
|
||
float fStaminaConsumeRate = 2.5f;
|
||
float fForgottenStaminaConsumeRate = 0.5f;
|
||
float fSuperSaveBonusRate = 2.0f; // 성장의 물약(구 슈퍼 세이버) 보너스 혜택 비율(EXP/JP)
|
||
int anSuperSaveLevelMinLimit[ 7 ] = { 1, 121, 131, 141, 1, 151, 1 }; // Minimum level restriction for Growth Potion (formerly Super Saber)
|
||
int anSuperSaveLevelMaxLimit[ 7 ] = { 120, 130, 140, 150, 150, 170, 300 }; // Maximum level restriction for Growth Potion (formerly Super Saber)
|
||
float fSummonStaminaSaveBonusRate[ 6 ] = { 0.5f, 0.0f, 0.5f, 1.0f, 1.5f, 2.0f }; // Bonus benefit rate (EXP) for summon stamina saber (O-gok Cracker)
|
||
|
||
float fAllyPCBangBonusRate = 0.1f; // 멤버쉽 PC방 혜택 비율(EXP/JP)
|
||
float fAllyPCBangChaosBonusRate = 0.1f; // 멤버쉽 PC방 혜택 비율(Lac)
|
||
float fPremiumPCBangBonusRate = 2.0f; // 더블 플러스 PC방 혜택 비율(EXP/JP)
|
||
float fPremiumPCBangChaosBonusRate = 0.1f; // 더블 플러스 PC방 혜택 비율(Lac)
|
||
float fPremiumPCBangGoldBonusDropRate = 2.0f; // 더블 플러스 PC방 혜택 비율(루피 드랍율, 기본값: 1.0)
|
||
float fPremiumPCBangItemBonusDropRate = 2.0f; // 더블 플러스 PC방 혜택 비율(아이템 드랍율, 기본값: 1.0)
|
||
float fPremiumPCBangChaosBonusDropRate = 2.0f; // 더블 플러스 PC방 혜택 비율(라크 드랍율, 기본값: 1.0)
|
||
bool bApplyStaminaBonusInPremiumPCBang = false; // 더블 플러스 PC방에서 스테미너 효과 적용 허용 여부
|
||
|
||
bool bUsePlayPoint = false; // 플레이 포인트 사용 여부
|
||
int nPlayPointAccumulateTerm = 60; // 플레이 포인트 누적 간격(분 단위)
|
||
int nPlayPointAccumulateAmount = 1; // 한 번의 플레이 포인트 누적시 누적될 포인트 량
|
||
float fPremiumPCBangPlayPointBonusRate = 2.0f; // Double Plus PC room benefit rate (play points)
|
||
|
||
bool bUseTimeBasedEventScript = false; // Whether to use script-based timed events
|
||
bool bUseTimeBasedEventDB = false; // DB SP-Based Timed Event Usage Availability
|
||
int nTermForTimeBasedEventScript = 60; // Script-based timed event firing interval (in minutes)
|
||
int nTermForTimeBasedEventDB = 60; // DB SP-based timed event firing interval (in minutes)
|
||
|
||
int nMinSpeed = 50;
|
||
int nItemHoldTime = 180000;
|
||
int nMaxCreatureLevel = 170;
|
||
|
||
float fMonsterRegen = 1.0f;
|
||
float fMonsterRegenBoss = 1.002187f;
|
||
|
||
float fSummonExpLimit = 4.0f;
|
||
int nSummonExpPenaltyLevel = 10;
|
||
|
||
float fKillImmoralPercentage = 1.0f;
|
||
float fKilledDrop = 0.3f;
|
||
float fKilledExpPercentage = 1.0f;
|
||
|
||
int nCrimeState = 1;
|
||
int nCrimeParty = 1;
|
||
|
||
int nBossEffect = 0;
|
||
|
||
int nException_AR = 0;
|
||
int nException_921 = 0;
|
||
float fException_31109 = 0.25f;
|
||
float fException_31309 = 0.0f;
|
||
|
||
int bIsLakGuard = 0;
|
||
|
||
int nItemExpertCube = 1;
|
||
int nItemExpertGrade = 1;
|
||
|
||
|
||
|
||
bool bIsNoCollisionCheck = false;
|
||
bool bSkipLoadingAttribute = false;
|
||
|
||
bool bMonsterWandering = true;
|
||
bool bMonsterCollisionToLine = true;
|
||
bool bMonsterPathFinding = false;
|
||
bool bLogMonsterPathFinding = true;
|
||
bool bLogSchedulingStatus = true;
|
||
bool bIgnoreSkillCoolTime = false;
|
||
bool bIsPKServer = false;
|
||
bool bHardcore = false;
|
||
float fHardcoreExpRate;
|
||
int nPKPenaltyLevel = 10;
|
||
bool bDisablePKOn = false; // If PK mode should be disabled completely
|
||
bool bIsAdultServer = false;
|
||
bool bRestrictSpeicialChar = true; // Whether the account restricts the use of special characters during login
|
||
std::string strAllowedSpecialChar = ""; // Special characters allowed in the account during login
|
||
|
||
bool bAutoOpen = false;
|
||
|
||
bool bDisableHuntaholic = false; // Huntaholic 가능 여부
|
||
|
||
bool bUseAutoJail = true; // 무저갱 사용 여부
|
||
|
||
int nSecuritySolutionType = 0; // Type de solution de sécurité (0 : Désactivé, 1 : Game Guard, 2 : Hack Shield, 3 : X-Trap)
|
||
AR_TIME nPeriodOfSecuritySolutionCheck = 5*60*100; // Cycle de vérification client/serveur de la solution de sécurité (5 minutes)
|
||
AR_TIME nSecuritySolutionResponseTimeout = 30*100; // Délai entre la demande de vérification du client/serveur de la solution de sécurité et la réponse (30 secondes)
|
||
std::string strSecuritySolutionExceptionalIP; // Une liste d'adresses IP qui n'appliquent pas la vérification client/serveur de la solution de sécurité (séparées par ;)
|
||
|
||
bool bDisableDungeonRaidSiege = false;
|
||
bool bIsCashUsableServer = false;
|
||
bool bUseAccountAuthorityDB = false;
|
||
bool bCashItemDropable = false;
|
||
bool bUseAutoTrap = true;
|
||
bool bBroadcastEventItemPickup = false;
|
||
bool bUseGuildDonationPoint = false;
|
||
bool bRestrictBanWordForBooth = false;
|
||
int nMinBoothStartableLevel = 0;
|
||
bool bLimitBoothOpenableLayerToZero = false; // Restrict opening markets to layer 0 only (prevents abuse of markets on the Island of Apprentices)
|
||
bool bDisableBuyBooth = false; // Feature to restrict the use of purchase markets
|
||
bool bDisableBooth = false;
|
||
bool bDisableTrade = false;
|
||
|
||
bool bLimitAdvChatCount = true;
|
||
int nMinGlobalChatUsableLevel = 0;
|
||
int nMaxStorageItemCount = 1000; // Maximum items in storage
|
||
int nMaxCharactersPerAccount = 8; // Max Characters per Account
|
||
|
||
bool bUseSecurityNo = false;
|
||
bool bUseSecurityNoForStorage = false;
|
||
bool bUseSecurityNoForDeletingCharacter = false;
|
||
bool bCheckStorageSecurityAlways = true;
|
||
|
||
bool bLimitFieldLogout = false;
|
||
AR_TIME nLogoutTimer = 1000;
|
||
|
||
// Force the reset of account login information if the kick process is not handled properly when the user attempts to log in
|
||
bool bForceUnregisterAccountOnKickFail = false;
|
||
|
||
bool bForbiddenScriptInitialized = false; // 채팅 창을 통해 입력된 명령어 중 무시할 명령어 목록 초기화 여부
|
||
// 채팅 창을 통해 스크립트 명령어 입력 시 이 값이 false면 설정값을 다시 파싱함
|
||
std::string strForbiddenScript; // 채팅 창을 통해 입력된 명령어 중 무시할 명령어 목록(행 구분자 = ';', 행 내의 항목 구분자 = ',')
|
||
|
||
bool bUseLoginLogoutDebug = false;
|
||
|
||
bool bLogVulcanusDungeon = false;
|
||
|
||
bool bLimitGameTime = false;
|
||
int nMaxGameTimeLimitedAge = 17;
|
||
AR_TIME nMaxHealthyGameTime = 1080000;
|
||
AR_TIME nMaxTiredGameTime = 1800000;
|
||
|
||
int nEtherealDurabilityBaseConsumptionOnNormalAttack = 31; // 에테리얼 내구도 평타 공격 시 기본 소모량
|
||
int nEtherealDurabilityBaseConsumptionOnSkillAttack = 51; // 에테리얼 내구도 스킬 공격 시 기본 소모량(버프 포함)
|
||
int nEtherealDurabilityBaseConsumptionOnDamage = 98; // 에테리얼 내구도 피격 시 기본 소모량
|
||
float nEtherealDurabilityConsumptionRate = 0.25f;
|
||
|
||
int nMaxLevel = 200; // Maximum character level
|
||
|
||
std::string strLogRequiredStateList; // 소멸 시 로그를 남겨야 하는 지속효과 ID 목록
|
||
std::string strLogRequiredItemList; // 기간 만료 시 로그를 남겨야 하는 아이템 ID 목록
|
||
|
||
AR_TIME nAuctionSearchRequestMinInterval = 300; // 경매 검색 반복 가능 최단 시간 간격(1/100 초 단위)
|
||
AR_TIME nAuctionProcessRequestMinInterval = 100; // 경매 일반 동작 반복 가능 최단 시간 간격(검색 제외 모두 적용)
|
||
|
||
int nFarmNormalSummonEXP = 145763; // 소환수 기본형의 농장 시간당 경험치
|
||
int nFarmGrowthSummonEXP = 1118029; // 소환수 성장형의 농장 시간당 경험치
|
||
int nFarmEvolveSummonEXP = 3708799; // 소환수 진화형의 농장 시간당 경험치
|
||
|
||
int nPremiumFarmNormalSummonEXP = 728814; // 소환수 기본형의 농장 시간당 경험치 (프리미엄 티켓 이용)
|
||
int nPremiumFarmGrowthSummonEXP = 13975356; // 소환수 기본형의 농장 시간당 경험치 (프리미엄 티켓 이용)
|
||
int nPremiumFarmEvolveSummonEXP = 37087982; // 소환수 기본형의 농장 시간당 경험치 (프리미엄 티켓 이용)
|
||
|
||
int nGuildBuffMinute = 60;
|
||
int nGuildDonateGold = 1000000;
|
||
|
||
float stamina_ratio[MAX_LEVEL];
|
||
|
||
int player_exp_limit[MAX_LEVEL];
|
||
|
||
int normal_summon_exp_limit[NORMAL_SUMMON_MAX_LEVEL];
|
||
int growth_summon_exp_limit[GROWTH_SUMMON_MAX_LEVEL];
|
||
int evolve_summon_exp_limit[EVOLVE_SUMMON_MAX_LEVEL];
|
||
|
||
int GetMaxWeight( int level, int strength )
|
||
{
|
||
// (2007-03-24 리뉴얼 적용)
|
||
return ( level + strength ) * 10;
|
||
}
|
||
|
||
XSpinLock block_account_lock;
|
||
std::vector< std::string > vBlockedAccount;
|
||
|
||
void RegisterBlockAccount( const char * szAccount )
|
||
{
|
||
THREAD_SYNCRONIZE( block_account_lock );
|
||
vBlockedAccount.push_back( szAccount );
|
||
|
||
FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "AutoBlockLog", "Auto-blocked account added: [%s]", szAccount );
|
||
}
|
||
|
||
void DeleteFromBlockAccount( const char * szAccount )
|
||
{
|
||
THREAD_SYNCRONIZE( block_account_lock );
|
||
|
||
for( std::vector< std::string >::iterator it = vBlockedAccount.begin(); it != vBlockedAccount.end(); ++it )
|
||
{
|
||
if( (*it) == szAccount )
|
||
{
|
||
vBlockedAccount.erase( it );
|
||
|
||
FileLogHandler::GetFileLogHandler()->LogStringEx( NULL, "AutoBlockLog", "Auto-blocked account removed: [%s]", szAccount );
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
bool IsBlockedAccount( const char * szAccount )
|
||
{
|
||
THREAD_SYNCRONIZE( block_account_lock );
|
||
|
||
for( std::vector< std::string >::iterator it = vBlockedAccount.begin(); it != vBlockedAccount.end(); ++it )
|
||
{
|
||
if( (*it) == szAccount )
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 물건을 주울 수 있는 거리
|
||
int GetPickableRange()
|
||
{
|
||
return 20;
|
||
}
|
||
|
||
// 등급에 따른 아이템 레벨 제한
|
||
int GetItemLevelLimitByRank( int item_rank )
|
||
{
|
||
static int _table[] =
|
||
{
|
||
0, 20, 50, 80, 100, 120, 140, 170
|
||
};
|
||
|
||
if( item_rank < 1 ) item_rank = 1;
|
||
if( item_rank > 8 ) item_rank = 8;
|
||
|
||
return _table[ item_rank-1 ];
|
||
}
|
||
|
||
// 등급별 아이템 권장레벨 조정 테이블
|
||
int GetItemRecommendModTable( int item_rank )
|
||
{
|
||
static int _table[] =
|
||
{
|
||
0, 3, 3, 2, 2, 3, 2, 2
|
||
};
|
||
|
||
if( item_rank < 1 ) item_rank = 1;
|
||
if( item_rank > 8 ) item_rank = 8;
|
||
|
||
return _table[ item_rank-1 ];
|
||
}
|
||
|
||
// 아이템 권장 레벨
|
||
int GetItemRecommendLevel( int item_rank, int item_level, int min_item_usable_level )
|
||
{
|
||
if( item_rank <= 1 ) return 0;
|
||
|
||
return std::min( std::max( GetItemLevelLimitByRank( item_rank ), min_item_usable_level ) + (item_level-1) * GetItemRecommendModTable( item_rank ), GetItemLevelLimitByRank( item_rank + 1 ) );
|
||
}
|
||
|
||
// 아이템 권장 레벨에 따른 페널티 비율
|
||
c_fixed10 GetItemLevelPenalty( int creature_level, int item_rank, int item_level, int min_item_usable_level )
|
||
{
|
||
c_fixed10 A;
|
||
A.set( 10000 );
|
||
c_fixed10 B;
|
||
B.set( 500 );
|
||
|
||
c_fixed10 result;
|
||
result.set( 10000 );
|
||
|
||
int recommend_level = GetItemRecommendLevel( item_rank, item_level, min_item_usable_level );
|
||
int limit_level = std::max( GetItemLevelLimitByRank( item_rank ), min_item_usable_level );
|
||
|
||
if( item_level == 1 ||
|
||
creature_level < limit_level ||
|
||
creature_level >= recommend_level )
|
||
{
|
||
return result;
|
||
}
|
||
|
||
if( creature_level < limit_level )
|
||
{
|
||
creature_level = limit_level;
|
||
}
|
||
|
||
result = static_cast< c_fixed10 >( recommend_level - creature_level ) / static_cast< c_fixed10 >( recommend_level - limit_level );
|
||
result = result * static_cast< c_fixed10 >( A - B * item_level );
|
||
|
||
return ( c_fixed10( 1 ) - result );
|
||
}
|
||
|
||
int GetDecreasedEndurancePoint( int previous_endurance, int current_endurance )
|
||
{
|
||
return ( ( previous_endurance + 99999 ) / 100000 ) - ( ( current_endurance + 99999 ) / 100000 );
|
||
}
|
||
|
||
const c_fixed10 GetEtherealDurabilityConsumeRate( const int nLevel, const int nJobID, const bool bIsAttack, const int nDamage )
|
||
{
|
||
c_fixed10 fConsumeRate;
|
||
|
||
// 아바타 레벨, 데미지 값에 의한 수치 먼저 계산
|
||
fConsumeRate.set( ( ( nLevel * 10 / 3 + 5 ) / 10 * 100 ) + ( ( nDamage * ( bIsAttack ? 1 : 20 ) + 90 ) / 100 * 100 ) );
|
||
|
||
// 직업에 대한 공격/피격 시 소모율 적용(직업 정보가 없거나 기본 직업이면 소모율 기본값 적용)
|
||
const JobInfo * pJobInfo = GameContent::GetJobInfo( nJobID );
|
||
if( pJobInfo && pJobInfo->job_depth )
|
||
{
|
||
switch( pJobInfo->job_class )
|
||
{
|
||
// 전사계열 보정치: 공격 시 45%, 피격 시 5%
|
||
case JobInfo::FIGHTER: fConsumeRate.set( fConsumeRate.get() + ( ( bIsAttack ) ? 2200 : 500 ) ); break;
|
||
// 헌터계열 보정치: 공격 시 20%, 피격 시 15%
|
||
case JobInfo::HUNTER: fConsumeRate.set( fConsumeRate.get() + ( ( bIsAttack ) ? 2000 : 1500 ) ); break;
|
||
// 마법사계열 보정치: 공격 시 10%, 피격 시 50%
|
||
case JobInfo::MAGICIAN: fConsumeRate.set( fConsumeRate.get() + ( ( bIsAttack ) ? 1000 : 5000 ) ); break;
|
||
// 소환사계열 보정치: 공격 시 25%, 피격 시 25%
|
||
case JobInfo::SUMMONER: fConsumeRate.set( fConsumeRate.get() + ( ( bIsAttack ) ? 2500 : 2500 ) ); break;
|
||
}
|
||
}
|
||
|
||
assert( fConsumeRate.get() % 100 == 0 );
|
||
|
||
return fConsumeRate;
|
||
}
|
||
|
||
const c_fixed10 GetEtherealDurabilityConsumeRateByItem( const int nRank, const int nGrade )
|
||
{
|
||
c_fixed10 fConsumeRate;
|
||
|
||
// 아이템 랭크별 소모율: (랭크 ^ 2) / 2 * 5% : / 2 에서 소수 1째 자리에서 올림
|
||
// 아이템 등급별 소모율: 등급 * 50%
|
||
fConsumeRate.set( ( nRank * nRank * 10 / 2 + 9 ) / 10 * 500 + nGrade * 5000 );
|
||
|
||
return fConsumeRate;
|
||
}
|
||
|
||
const int GetEtherealDurabilityBaseConsumption( const bool bIsAttack, /*StructCreature::DamageType*/ const int nDamageType )
|
||
{
|
||
int nBaseConsumption = 0;
|
||
|
||
if( bIsAttack )
|
||
{
|
||
switch( nDamageType )
|
||
{
|
||
case StructCreature::DT_NORMAL_PHYSICAL_DAMAGE:
|
||
case StructCreature::DT_NORMAL_PHYSICAL_LEFT_HAND_DAMAGE:
|
||
nBaseConsumption = nEtherealDurabilityBaseConsumptionOnNormalAttack;
|
||
break;
|
||
case StructCreature::DT_NORMAL_PHYSICAL_SKILL_DAMAGE:
|
||
case StructCreature::DT_NORMAL_MAGICAL_DAMAGE:
|
||
case StructCreature::DT_STATE_PHYSICAL_DAMAGE:
|
||
case StructCreature::DT_STATE_MAGICAL_DAMAGE:
|
||
nBaseConsumption = nEtherealDurabilityBaseConsumptionOnSkillAttack;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
nBaseConsumption = nEtherealDurabilityBaseConsumptionOnDamage;
|
||
|
||
return nBaseConsumption;
|
||
}
|
||
|
||
// 각 레벨에 따른 성능 조정 (데미지, 방어구 동일)
|
||
const c_fixed10 GetItemValue( c_fixed10 item_current_value, int item_rank_value, int creature_level, int item_rank, int item_level, int min_item_usable_level )
|
||
{
|
||
c_fixed10 v = static_cast< c_fixed10 >( item_current_value - item_rank_value ) * GetItemLevelPenalty( creature_level, item_rank, item_level, min_item_usable_level ) + item_rank_value;
|
||
|
||
return v;
|
||
}
|
||
|
||
const c_fixed10 GetDonationRewardMoralPoint( const __int64 & nDonateGoldAmount )
|
||
{
|
||
c_fixed10 fReward;
|
||
fReward.set( nDonateGoldAmount );
|
||
|
||
c_fixed10 fRate;
|
||
fRate.set( DONATE_GOLD_UNIT_COUNT );
|
||
|
||
fReward /= fRate;
|
||
return fReward;
|
||
}
|
||
|
||
const StructGold GetItemSellPrice( const StructGold & price, const int rank, const int lv, const bool same_price_for_buying, const bool ethereal_durability_exhausted, const bool is_equipment )
|
||
{
|
||
StructGold add_price( 0 );
|
||
|
||
// 표준가격 증가값 계산 (1~8랭크 모두 통합)
|
||
StructGold k( price );
|
||
|
||
if( rank > 8 )
|
||
{
|
||
assert(false && "8랭크를 넘는 아이템의 가격을 측정하려 했숨다.: GameRule.cpp:GetItemSellPrice");
|
||
return StructGold( 0 );
|
||
}
|
||
|
||
// 장비품의 경우 대장장이 강화 위해 쓴 돈까지 계산해준다.
|
||
if( is_equipment )
|
||
{
|
||
// 2013.06.03 - 기획팀에서 사용하고 있던 계산 테이블과 서버에서 가지고 있던 테이블이 달라 기획쪽테이블로 맞춤
|
||
float f[] = {1.35f, 0.2f, 0.115f, 0.092f, 0.085f, 0.1f, 0.1f, 0.1f}; // 랭크당 증가가격용 팩터 테이블 (from 기획팀- NPC_ItemUp.lua)
|
||
|
||
// 팔 때는 기본이 +1레벨이므로 +2레벨부터는 강화에 사용된 돈 까지 계산해준다.
|
||
for( int i = 2; i <= lv; i++ )
|
||
{
|
||
if( rank == 0 ) add_price.SetRawData( (__int64)(k.GetRawData() * f[rank] * 0.1f) * 10 );
|
||
else if( rank == 1 ) add_price.SetRawData( (__int64)(k.GetRawData() * f[rank-1] * 0.1f) * 10 );
|
||
else if( rank == 2 ) add_price.SetRawData( (__int64)(k.GetRawData() * f[rank-1] * 0.01f) * 100 );
|
||
else add_price.SetRawData( (__int64)(k.GetRawData() * f[rank-1] * 0.001f) * 1000 );
|
||
|
||
k += add_price;
|
||
}
|
||
}
|
||
|
||
// 최종적인 Lv별 표준가는 원래가격 + 추가가격 (Lv1일땐 add_price = 0이 할당된 상태임)
|
||
return ( k.GetRawData() * ( same_price_for_buying ? 1.0f : ITEM_SELL_RATIO ) ) / ( ( ethereal_durability_exhausted ) ? 20 : 1 );
|
||
}
|
||
|
||
bool IsValidName( int code_page, const char * name, int nBufferSize, int nLimitMin, int nLimitMax )
|
||
{
|
||
// 유니코드 변환 및 각종 체크 전에 길이 체크 우선(Multibyte 스트링에서 수행해야 함)
|
||
if( static_cast< int >( strlen( name ) ) < nLimitMin || static_cast< int >( strlen( name ) ) > nLimitMax )
|
||
return false;
|
||
|
||
// 버퍼 길이가 최대 길이(널 포함)보다 길면 널 문자까지로 조정(그 뒤는 검사할 필요 없음)
|
||
if( nBufferSize > nLimitMax+1 )
|
||
nBufferSize = nLimitMax+1;
|
||
|
||
wchar_t buf[1024];
|
||
|
||
if( code_page <= 0 )
|
||
{
|
||
code_page = ENV().GetInt( "CodePage", CP_ACP );
|
||
}
|
||
|
||
MultiByteToWideChar( code_page, 0, name, nBufferSize, buf, 1024 );
|
||
|
||
wchar_t* c = buf;
|
||
|
||
int cnt = 0;
|
||
|
||
// 이름에 사용되는 언어를 한 가지로 제한해야 하는 국가를 위해 등장한 문자들을 비트셋으로 체크
|
||
enum APPEARED_LANGUAGE
|
||
{
|
||
APPEARED_ENGLISH = 1 << 0,
|
||
APPEARED_NATIVE = 1 << 1,
|
||
};
|
||
int nAppeared = 0;
|
||
|
||
for( int i=0; ; i++,c++,cnt++ )
|
||
{
|
||
// 허용 가능한 이름(2Byte 문자는 아래의 조건문에서 cnt 증가 후 루프에서 또 증가하므로 2개 증가)
|
||
if( *c == L'\0' )
|
||
{
|
||
// 길이 한 번 더 체크
|
||
if( cnt < nLimitMin || cnt > nLimitMax )
|
||
return false;
|
||
else
|
||
break;
|
||
}
|
||
|
||
// 숫자, 영문의 경우는 pass
|
||
if( L'0' <= *c && *c <= L'9' )
|
||
continue;
|
||
|
||
if( ( L'a' <= *c && *c <= L'z' ) || ( L'A' <= *c && *c <= L'Z' ) )
|
||
{
|
||
nAppeared |= APPEARED_ENGLISH;
|
||
continue;
|
||
}
|
||
|
||
nAppeared |= APPEARED_NATIVE;
|
||
|
||
if( code_page == CP_HONGKONG ) // 홍콩용 (BIG5)
|
||
{
|
||
if( 0x2E80 <= *c && *c <= 0x2EF3 ) { cnt++; continue; } // CJK Radicals Supplement
|
||
if( 0x2F00 <= *c && *c <= 0x2FD5 ) { cnt++; continue; } // Kangxi Radicals
|
||
if( 0x3105 <= *c && *c <= 0x312C ) { cnt++; continue; } // Bopomofo
|
||
if( 0x31A0 <= *c && *c <= 0x31B7 ) { cnt++; continue; } // Bopomofo Extended
|
||
if( 0x3400 <= *c && *c <= 0x4DB5 ) { cnt++; continue; } // CJK Ideographs Ext. A
|
||
if( 0x4E00 <= *c && *c <= 0x9FBB ) { cnt++; continue; } // Unified CJK Ideographs
|
||
if( 0xF900 <= *c && *c <= 0xFAD9 ) { cnt++; continue; } // CJK Compatibility Ideographs
|
||
}
|
||
else if( code_page == CP_JAPAN ) // 일본용 (Shift-JIS)
|
||
{
|
||
if( 0x2E80 <= *c && *c <= 0x2EF3 ) { cnt++; continue; } // CJK Radicals Supplement
|
||
if( 0x2F00 <= *c && *c <= 0x2FD5 ) { cnt++; continue; } // Kangxi Radicals
|
||
if( 0x3041 <= *c && *c <= 0x3093 ) { cnt++; continue; } // Hiragana
|
||
if( 0x30A1 <= *c && *c <= 0x30FA ) { cnt++; continue; } // Katakana
|
||
if( 0x31F0 <= *c && *c <= 0x31FF ) { cnt++; continue; } // Katakana Phonetic Ext.
|
||
if( 0x3005 == *c ) { cnt++; continue; } // Ideographic iteration mark
|
||
if( 0x30FC == *c ) { cnt++; continue; } // Hiragana, Katakana Prolonged sound mark
|
||
if( 0x30FB == *c ) { cnt++; continue; } // Katakana Middle Dot
|
||
if( 0x3400 <= *c && *c <= 0x4DB5 ) { cnt++; continue; } // CJK Ideographs Ext. A
|
||
if( 0x4E00 <= *c && *c <= 0x9FBB ) { cnt++; continue; } // Unified CJK Ideographs
|
||
if( 0xF900 <= *c && *c <= 0xFAD9 ) { cnt++; continue; } // CJK Compatibility Ideographs
|
||
}
|
||
else if( code_page == CP_CHINA ) // 중국용 (GB2312 - Chinese Simplified)
|
||
{
|
||
if( 0x2E80 <= *c && *c <= 0x2EF3 ) { cnt++; continue; } // CJK Radicals Supplement
|
||
if( 0x2F00 <= *c && *c <= 0x2FD5 ) { cnt++; continue; } // Kangxi Radicals
|
||
if( 0x3105 <= *c && *c <= 0x312C ) { cnt++; continue; } // Bopomofo
|
||
if( 0x31A0 <= *c && *c <= 0x31B7 ) { cnt++; continue; } // Bopomofo Extended
|
||
if( 0x3400 <= *c && *c <= 0x4DB5 ) { cnt++; continue; } // CJK Ideographs Ext. A
|
||
if( 0x4E00 <= *c && *c <= 0x9FBB ) { cnt++; continue; } // Unified CJK Ideographs
|
||
if( 0xF900 <= *c && *c <= 0xFAD9 ) { cnt++; continue; } // CJK Compatibility Ideographs
|
||
}
|
||
else if( code_page == CP_RUSSIA ) // 러시아용
|
||
{
|
||
if( ( 0x0401 <= *c && *c <= 0x040C ) ||
|
||
( 0x040E <= *c && *c <= 0x040F ) ||
|
||
( 0x0410 <= *c && *c <= 0x044F ) ||
|
||
( 0x0451 <= *c && *c <= 0x045C ) ||
|
||
( 0x045E <= *c && *c <= 0x045F ) ||
|
||
( 0x0490 <= *c && *c <= 0x0491 ) )
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
}
|
||
else if( code_page == CP_WEST_EUROPE ) // 서유럽용 ( ANSI - Latin I / West European Latin )
|
||
{
|
||
if( 0x00C0 <= *c && *c <= 0x00FF ) // C1 Controls and Latin-1 Supplement )
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
}
|
||
else if( code_page == CP_MIDEAST ) // 중동용 (Arabic - Windows)
|
||
{
|
||
if( ( 0x0621 <= *c && *c <= 0x063A ) ||
|
||
( 0x0641 <= *c && *c <= 0x064A ) || // Based on ISO 8859-6
|
||
( 0x0671 <= *c && *c <= 0x0678 ) || // Extended Arabic Letters
|
||
( 0x061B <= *c && *c <= 0x061F ) ||
|
||
( 0x066A <= *c && *c <= 0x066D ) ||
|
||
0x060C == *c || 0x060D == *c ) // Punctuation
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
}
|
||
else if( code_page == CP_TURKEY )
|
||
{
|
||
if( *c == 0x0130 || *c == 0x0131 ||
|
||
*c == 0x011E || *c == 0x011F ||
|
||
*c == 0x015E || *c == 0x015F ||
|
||
*c == 0x00D6 || *c == 0x00DC ||
|
||
*c == 0x00F6 || *c == 0x00FC ||
|
||
*c == 0x00C7 || *c == 0x00E7 )
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
|
||
}
|
||
else if( code_page == CP_THAILAND )
|
||
{
|
||
if( ( 0x0E01 <= *c && *c <= 0x0E0D ) ||
|
||
( 0x0E0F <= *c && *c <= 0x0E59 ) ||
|
||
*c == L'_' )
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
}
|
||
else if( code_page == CP_CENTRAL_EUROPE )
|
||
{
|
||
if( ( 0x0104 <= *c && *c <= 0x0107 ) ||
|
||
( 0x0118 <= *c && *c <= 0x0119 ) ||
|
||
( 0x0141 <= *c && *c <= 0x0144 ) ||
|
||
( 0x015A <= *c && *c <= 0x015B ) ||
|
||
( 0x0179 <= *c && *c <= 0x017C ) )
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
|
||
if( ( 0x00D3 == *c ) ||
|
||
( 0x00F3 == *c ) )
|
||
{
|
||
// 해당 국가의 Codepage로 인코딩하면 1 Byte 문자이므로 cnt를 증가시키면 안 됨.
|
||
continue;
|
||
}
|
||
}
|
||
else // 국내용
|
||
{
|
||
//if( 0x1100 <= *c && *c < 0x115A ) { cnt++; continue; }
|
||
//if( 0x115F <= *c && *c < 0x11A3 ) { cnt++; continue; }
|
||
//if( 0x11A8 <= *c && *c < 0x11FA ) { cnt++; continue; }
|
||
//if( 0x302E <= *c && *c < 0x3030 ) { cnt++; continue; }
|
||
//if( 0x3131 <= *c && *c < 0x318F ) { cnt++; continue; }
|
||
if( 0xAC00 <= *c && *c < 0xD7A4 ) { cnt++; continue; }
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 이름에 사용되는 언어를 한 가지로 제한
|
||
switch( code_page )
|
||
{
|
||
case CP_RUSSIA:
|
||
if( nAppeared == ( APPEARED_ENGLISH | APPEARED_NATIVE ) )
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool ReformatName( char * name )
|
||
{
|
||
const int nNameLength = static_cast< int >( strlen( name ) );
|
||
if ( nNameLength < 1 )
|
||
return true;
|
||
|
||
int code_page = ENV().GetInt( "CodePage", CP_ACP );
|
||
|
||
switch ( code_page )
|
||
{
|
||
case CP_WEST_EUROPE : // 서유럽(프랑스/독일)
|
||
case CP_CENTRAL_EUROPE: // 중유럽(폴란드)
|
||
case CP_TURKEY: // 터키
|
||
{
|
||
// 첫문자는 대문자, 나머지는 소문자로 변환
|
||
// * 대/소문자 변환 처리 중 오류 발생을 방지하기 위해 유니코드로 변환하고 처리
|
||
wchar_t wszName[ 32 ];
|
||
if( !MultiByteToWideChar( code_page, 0, name, -1, wszName, _countof( wszName ) ) )
|
||
return false;
|
||
|
||
s_tolower( wszName, _countof( wszName ) );
|
||
wszName[ 0 ] = towupper( wszName[ 0 ] );
|
||
|
||
if( !WideCharToMultiByte( code_page, 0, wszName, -1, name, nNameLength + 1, NULL, NULL ) )
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool IsValidPartyName( const char * name, int nBufferSize, int nLimitMin, int nLimitMax )
|
||
{
|
||
if( nBufferSize > nLimitMax+1 ){ nBufferSize = nLimitMax+1; }
|
||
|
||
unsigned char* c = (unsigned char*)name;
|
||
|
||
for( int i=0; i<=nBufferSize; i++,c++ )
|
||
{
|
||
// 허용가능한 이름
|
||
if( *c == 0 ) {
|
||
if( i < nLimitMin ) { return false; } else { return true; }
|
||
}
|
||
|
||
// 숫자, 영문의 경우는 pass
|
||
if((*c >= '0' && *c <= '9') || ( *c >= 'a' && *c <= 'z' ) || ( *c >= 'A' && *c <= 'Z' ) || ( *c == ' ' ) )
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 첫코드가 한글이다.
|
||
if( *c >= 0xb0 && *c <= 0xc8 )
|
||
{
|
||
if( (i+1) >= nBufferSize ) { return false; }
|
||
c++;i++;
|
||
|
||
// 두번째 코드도 한글이면 pass
|
||
if( *c >= 0xa1 && *c <= 0xfe ) { continue; }
|
||
}
|
||
|
||
// 숫자, 영문, 한글이 아니므로 허용불가.
|
||
return false;
|
||
}
|
||
// 제한길이를 넘어선 이름이므로 허용불가.
|
||
return true;
|
||
}
|
||
|
||
float GetStaminaRatio( int level )
|
||
{
|
||
if( level < 1 )
|
||
level = 1;
|
||
|
||
if( level > MAX_LEVEL )
|
||
level = MAX_LEVEL;
|
||
|
||
if( !stamina_ratio[level-1] )
|
||
{
|
||
// 2006-11-09 벨런스 조정에 의한 업데이트로 변경
|
||
//stamina_ratio[level-1] = (int) ( pow( (float) (level + 2), 1.8f ) * 2 - pow( (float) (level + 2) , 1.75f ) * 2 + 2 ) * 0.00055;
|
||
//stamina_ratio[level-1] = (int) ( level * 2.4 + pow( (float) (level), 1.46f ) + ( pow( (float) (level), 2 ) * 0.1f ) + 2 ) * 0.00055;
|
||
stamina_ratio[level-1] = (int) ( level * 2 + pow( (float) level, fStaminaConsumeRate ) * 0.00055 );
|
||
|
||
}
|
||
return stamina_ratio[level-1];
|
||
|
||
}
|
||
|
||
int GetSummonEXPLimit( int level )
|
||
{
|
||
return ( (int) pow( level, fSummonExpLimit ) );
|
||
}
|
||
|
||
int GetPlayerEXPLimit( int level )
|
||
{
|
||
if( !player_exp_limit[level-1] )
|
||
{
|
||
// 2006-12-11 상향 조정
|
||
//player_exp_limit[level-1] = (int)( pow( (float) level, 1.8f ) * 5.0f ) + 40;
|
||
player_exp_limit[level-1] = (int)( pow( (float) level, 1.8f ) * 30.0f ) + 240;
|
||
|
||
player_exp_limit[level-1] += (int)( player_exp_limit[level-1] * 0.1 * (level / 100) );
|
||
}
|
||
|
||
return player_exp_limit[level-1];
|
||
}
|
||
|
||
float GetSummonLevelPenalty( int master_level, int summon_level )
|
||
{
|
||
int level_diff = summon_level - master_level;
|
||
|
||
if( level_diff > 0 )
|
||
{
|
||
if( level_diff >= 30 )
|
||
{
|
||
return 10.0f;
|
||
}
|
||
|
||
return (int) ( level_diff * 0.25f * ( 2.34f - ( level_diff / 30.0f ) ) * 10.0f ) / 10.0f;
|
||
}
|
||
|
||
return level_diff;
|
||
}
|
||
|
||
float GetSummonStatPenalty( int master_level, int summon_level )
|
||
{
|
||
int level_diff = summon_level - master_level;
|
||
|
||
if( level_diff > 0 )
|
||
{
|
||
if( level_diff >= 50 )
|
||
{
|
||
return 0.7f;
|
||
}
|
||
|
||
return ( (int) ( ( 50.0f - level_diff ) * 0.6f ) + 70.0f ) / 100.0f;
|
||
}
|
||
|
||
return 1.0f;
|
||
}
|
||
|
||
int AppendOnetimePassword( char * pBuf, size_t buf_len, int one_time_key, int nSID, int nAccountID )
|
||
{
|
||
struct TempStructForEncode
|
||
{
|
||
int nSeed;
|
||
int n1;
|
||
int n2;
|
||
int n3;
|
||
int nChecksum;
|
||
} temp;
|
||
|
||
temp.nSeed = 384723432;
|
||
temp.n1 = one_time_key;
|
||
temp.n2 = nSID;
|
||
temp.n3 = nAccountID;
|
||
temp.nChecksum = one_time_key + nSID + nAccountID;
|
||
|
||
temp.n1 ^= temp.nSeed;
|
||
temp.n2 ^= temp.n1;
|
||
temp.n3 ^= temp.n2;
|
||
temp.nChecksum ^= temp.n3;
|
||
|
||
temp.n1 ^= 0xD8FB51A9;
|
||
temp.n2 ^= 0x9DC720AC;
|
||
temp.n3 ^= 0x31F42CB7;
|
||
temp.nChecksum ^= 0x7F9B3D2E;
|
||
|
||
s_sprintf( pBuf, buf_len, "%08X%08X%08X%08X%08X", temp.nSeed, temp.n1, temp.n2, temp.n3, temp.nChecksum );
|
||
return (int)strlen( pBuf );
|
||
}
|
||
|
||
c_fixed10 GetGameTimeLimitPenalty( AR_TIME continuous_play_time )
|
||
{
|
||
c_fixed10 fGameTimeLimitPenalty = 0;
|
||
|
||
if( !bLimitGameTime )
|
||
fGameTimeLimitPenalty.set( 10000 );
|
||
|
||
if( continuous_play_time < nMaxHealthyGameTime )
|
||
{
|
||
fGameTimeLimitPenalty.set( 10000 );
|
||
}
|
||
else if( continuous_play_time < nMaxTiredGameTime )
|
||
{
|
||
fGameTimeLimitPenalty.set( 5000 );
|
||
}
|
||
|
||
return fGameTimeLimitPenalty;
|
||
}
|
||
|
||
const int GetPetShovelingRewardStateCode()
|
||
{
|
||
static const int sRewardStateTable[ 2 ][ 4 ] = {
|
||
StructState::PET_SHOVELING_REWARD_INC_MOVE_SPEED, StructState::PET_SHOVELING_REWARD_INC_STR_INT, StructState::PET_SHOVELING_REWARD_INC_AGI_DEX, StructState::PET_SHOVELING_REWARD_INC_VIT,
|
||
StructState::PET_SHOVELING_REWARD_DEC_MOVE_SPEED, StructState::PET_SHOVELING_REWARD_DEC_STR_INT, StructState::PET_SHOVELING_REWARD_DEC_AGI_DEX, StructState::PET_SHOVELING_REWARD_DEC_VIT };
|
||
|
||
return sRewardStateTable[ XRandom( 0, 9 ) >= 7 ][ XRandom( 0, 3 ) ];
|
||
}
|
||
|
||
const c_fixed10 GetDifficultyBonus( const unsigned char nDifficulty )
|
||
{
|
||
static const c_fixed10 sDifficultyBonusTable[] =
|
||
{
|
||
c_fixed10( 1.0 ),
|
||
c_fixed10( 1.5 ),
|
||
c_fixed10( 2.0 )
|
||
};
|
||
|
||
assert( nDifficulty < sizeof( sDifficultyBonusTable ) / sizeof( c_fixed10 ) );
|
||
|
||
return sDifficultyBonusTable[ nDifficulty ];
|
||
}
|
||
|
||
int GetBattleArenaTeamNameStringID( int nTeamNo, bool bWithColorTag )
|
||
{
|
||
static const int sTeamName[ 2 ][ BATTLE_ARENA_MAX_TEAM_COUNT ] =
|
||
{
|
||
{
|
||
2382, // "연합군"
|
||
2383 // "마녀군"
|
||
},
|
||
{
|
||
2494, // <#00aeef>'연합군'
|
||
2495, // <#f26522>'마녀군'
|
||
}
|
||
};
|
||
|
||
if( nTeamNo < 0 || nTeamNo >= BATTLE_ARENA_MAX_TEAM_COUNT )
|
||
{
|
||
assert( 0 );
|
||
return 88; // "무엇인가" 의 String ID
|
||
}
|
||
|
||
return sTeamName[ ( !bWithColorTag ) ? 0 : 1 ][ nTeamNo ];
|
||
}
|
||
|
||
time_t GetBattleArenaBlockDuration( int nPenaltyCount )
|
||
{
|
||
static const time_t sBlockDuration[] = {
|
||
60 * 8,
|
||
60 * 16,
|
||
60 * 60,
|
||
60 * 120,
|
||
60 * 240,
|
||
60 * 480,
|
||
60 * 600
|
||
};
|
||
|
||
// 페널티 횟수가 0이면 입장 제한 페널티도 없음
|
||
if( nPenaltyCount <= 0 )
|
||
return 0;
|
||
else if( nPenaltyCount > _countof( sBlockDuration ) )
|
||
nPenaltyCount = _countof( sBlockDuration );
|
||
|
||
// 페널티 횟수 0은 입장 제한 없고, 1부터 페널티가 부여되는 것이므로 nPenaltyCount - 1 번째
|
||
return sBlockDuration[ nPenaltyCount - 1 ];
|
||
}
|
||
|
||
time_t GetBattleArenaPenaltyDuration( int nPenaltyCount )
|
||
{
|
||
static const time_t sPenaltyDuration[] = {
|
||
60 * 60,
|
||
60 * 60,
|
||
60 * 120,
|
||
60 * 240,
|
||
60 * 480,
|
||
60 * 960,
|
||
60 * 1200
|
||
};
|
||
|
||
// 페널티 횟수가 0이면 페널티 감소 기간도 없음
|
||
if( nPenaltyCount <= 0 )
|
||
return 0;
|
||
else if( nPenaltyCount > _countof( sPenaltyDuration ) )
|
||
nPenaltyCount = _countof( sPenaltyDuration );
|
||
|
||
// 페널티 횟수 0은 페널티 감소 기간 없고, 1부터 기간이 부여되는 것이므로 nPenaltyCount - 1 번째
|
||
return sPenaltyDuration[ nPenaltyCount - 1 ];
|
||
}
|
||
|
||
};
|
||
// End of namespace GameRule
|