Files
2026-06-01 12:46:52 +02:00

934 lines
31 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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