#define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #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