------------------------------------------------------------------- --===============================================================-- --= =-- --= RampageAI =-- --= by フェイスフル[信念者](Faithful) =-- --= (ロバート・グリム) =-- --= =-- --= http://gorgerush.net/faithful/RampageAI/ =-- --= =-- --= translated by silica as 冬物語の人 =-- --= http://winter.sgv417.jp/ =-- --= =-- --===============================================================-- -- -- -- このカスタムAIは、十分なクレジットで明示される他スクリプトや -- -- 人々からのいくつかの"借りた"コード"と共に、一から完全に -- -- 書かれています。 -- -- -- --===============================================================-- -- -- -- このスクリプトはできるだけ速く、効率的で、カスタマイズ可能に -- -- なるよう努力して書かれました。そのソースを閲覧して希望を -- -- 持って、何かを遠慮なく学んでください。 -- -- サードパーティプログラムがCtrl-1から9までを加えるような -- -- 戦術を用いることに私は個人的に反対しているので、 -- -- ベーススクリプトの一部にどんな「特徴」も見つけ出せない -- -- でしょう。 -- -- -- --===============================================================-- -- -- -- 私は、改造内容を"Custom.lua"ファイル中に含めるようにする -- -- ことを強く勧めます。このスクリプトの、より新しいバージョンが -- -- 出て来たとき、すぐに改造内容をより新しいバージョンに容易に -- -- 移せるように。また、そうすればあなたは改造内容を容易に共有 -- -- 出来るでしょう。 -- -- -- -- そしてはい、あなたがそうしなければならないと感じるなら、 -- -- あなたは私が反対する(上で、述べられている)改造を加えることが -- -- できます。 -- -- -- --===============================================================-- -- -- -- 楽しんでください! -- -- -- --===============================================================-- ------------------------------------------------------------------- --[[ このファイルはRampageAIの一部です。 RampageAIはフリーソフトです; これをFree Software Foundationが公布する GNU一般共有使用許諾の条件のもとで再配布および改変することができます; GNUライセンスバージョン2、または(あなたの選択で)2より以前のバージョンに おいて。 RampageAIは、有用であるという希望を持って配布されます。 ただし*保証なしで*; 市販性か、特定の目的のための適性の暗黙的な保証がなくても。 その他の詳細に関しては、GNU一般共有使用許諾を見てください。 RampageAIと共にGNUの一般共有使用許諾のコピーを同時に受け取るべきです; そうでないなら、以下の所へ書きなさい。 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA --]] --------------------- -- スクリプト位置 -- --------------------- -- この変数を変更して、スクリプトが保存されるディレクトリを反映してください。 -- (Fallen~Angel様の要望) ScriptLocation = "./AI/USER_AI/" --------------------- -- サポートスクリプト -- --------------------- -- 基本サポートオブジェクト require (ScriptLocation .. "List.lua") require (ScriptLocation .. "Table.lua") require (ScriptLocation .. "Timeout.lua") -- 定数とグローバル require (ScriptLocation .. "Const.lua") -- GRAVITY提供 require (ScriptLocation .. "Const2.lua") -- RampageAI特殊化 require (ScriptLocation .. "Globals.lua") -- 変更可能なグローバル -- 関数サポートスクリプト require (ScriptLocation .. "AntiPosLag.lua") -- ラグによる位置ズレ修正 require (ScriptLocation .. "AutoAlch.lua") -- オートアルケミストスキル require (ScriptLocation .. "Battle.lua") -- ターゲッティング / 攻撃中 require (ScriptLocation .. "Commands.lua") -- コマンド過程 require (ScriptLocation .. "ConfCheck.lua") -- 環境設定検証 require (ScriptLocation .. "Friends.lua") -- 友達承認 require (ScriptLocation .. "InitSupport.lua") -- サポート初期化 require (ScriptLocation .. "MonSupport.lua") -- モンスターサポート関数 require (ScriptLocation .. "NetUsageMonitor.lua") -- ネットワーク使用モニタニング require (ScriptLocation .. "Personality.lua") -- パーソナリティサポート require (ScriptLocation .. "Sequencer.lua") -- 順序 require (ScriptLocation .. "Utils.lua") -- その他 -- デバッグサポートスクリプト require (ScriptLocation .. "Simulator.lua") -- RO関数シミュレータ -- ホムンクルス特殊スクリプト require (ScriptLocation .. "Amistr.lua") require (ScriptLocation .. "Filir.lua") require (ScriptLocation .. "Lif.lua") require (ScriptLocation .. "Vanilmirth.lua") -- 環境設定 require (ScriptLocation .. "Conf/Config.lua") require (ScriptLocation .. "Conf/Monsters.lua") -- モンスター選択 -- ユーザー特殊スクリプト require (ScriptLocation .. "Conf/Custom.lua") -------------------- -- AI開始猶予 -- -------------------- -- これはFallen~Angel様からの発案です。 function AI(id) -- ミリ秒カウント取得 local ticks = GetTick() -- Check if we've waited any time if AIStartedAt == 0 then -- We haven't yet, set the begin time AIStartedAt = ticks -- Do any pre-AI delay initialization AI_PreDelayInit() -- そのログ Log(0,"Delaying AI for %d milliseconds...", AIStartWait) end -- Check if we've waited long enough if ticks - AIStartedAt >= AIStartWait then -- We've waited, never wait again AI = AI_track -- Call the new AI function AI(id) end end -------------------------- -- パフォーマンス追跡 -- -------------------------- function AI_track(id) -- 開始ミリ秒取得 local start = GetTick() -- もしそれが最後のサイクルかどうか、チェックする if CycleTimeTick ~= 0 then -- Update our time-between variable CycleTimeBetween = CycleTimeBetween + start - CycleTimeTick end -- メインAI呼び出し mainAI(id) -- 終了ミリ秒取得 local endtime = GetTick() -- 変数更新 CycleTimeTotal = CycleTimeTotal + (endtime - start) CycleTimeCount = CycleTimeCount + 1 CycleTimeTick = endtime end -------------------- -- 初期化 -- -------------------- --[[ The PreDelayInit() is meant to initialize only sections of the script that require >>absolutely no<< information about the homunculus itself. The delay is there to allow the RO client to initialize its own data before the AI script begins to poll for information --]] function AI_PreDelayInit() -- デバッグをする if Debug ~= true then -- Make the Log function exit immediately Log = NoReturn -- パフォーマンス追跡しない AI_track = mainAI else -- デバックレベルをチェックする if type(DebugLevel) ~= "number" then DebugLevel = 0 end -- traceai.txt ファイルがおかしくない? if SingleLineLogs == true then Log = LogWorkaround else -- おかしくなければ、以前のAIを付け加えるようにする -- Lets make it easier to distinguish Log(0,"\r\n\r\n\r\nAI reset.\r\n") end -- DebugMark オプションを調べる if type(DebugMark) ~= "number" then DebugMark = 0 end -- Setup a timeout for logging every X seconds if DebugMark >= 2 then -- Check if we want to track network usage generated by the homunculus script local debugMarkFunc if EnableNetUsageMonitor ~= true then -- We don't, so set the function to not use it debugMarkFunc = function(ticks) Log(2,"\r\n\t-- mark (%d ms since last, avg %f ms cycle, " .. "avg %f ms between) --", ticks,CycleTimeTotal / CycleTimeCount, CycleTimeBetween / CycleTimeCount) CycleTimeTotal = 0 CycleTimeCount = 0 CycleTimeBetween = 0 end else -- Set the function to display network usage caused by homunculus debugMarkFunc = function(ticks) Log(2,"\r\n\t-- mark (%d ms since last, avg %f ms cycle, " .. "avg %f ms between, ~%d packets sent) --", ticks,CycleTimeTotal / CycleTimeCount, CycleTimeBetween / CycleTimeCount, NetworkPacketsSent) CycleTimeTotal = 0 CycleTimeCount = 0 CycleTimeBetween = 0 NetworkPacketsSent = 0 end end -- Add the mark function to our timeouts Timeout.add(DebugMark, debugMarkFunc, true) else -- Since we don't care, replace the AI() function replaceAI = true end end -- Debug Log(0,"Initializing homunculus AI...") -- Check our configuration options CheckConfig() -- Setup basic handling SpecificAI = NoReturn -- no AI function SEQ_Custom = FalseReturn -- no custom sequences CMD_CustAdvMove = FalseReturn -- no custom advanced move commands ProcessCommand = AI_Command -- Process basic GetMsg()s GetHomunMotion = AI_HomunMotion -- Get the homunculus's motion -- Setup our check to auto-use a homunculus's healing/support skills -- (this is Homunculus specific) HomunSkillCheck = FalseReturn -- Seed the random number generator math.randomseed(GetTick()) Log(1,"First random number from this seed: %q",tostring(math.random())) -- See if the homunculus should try to stay ahead of the alchemist if HomunRunAhead then -- Set the function to check the distance CheckDistance = AI_CheckDistance_Lead else -- Set the function to check the distance CheckDistance = AI_CheckDistance_Follow end -- See if we should default to agressive if DefaultAggro then Mode = MODE_AGGRO end -- Call our modular initializations InitNetworkUsageMonitor() InitAntiPosLag() InitAttackLog() InitAcquire() InitKillSteal() InitFriends() InitAmountOrPercent() InitManualSkills() InitOwnerSkills() InitPersonality() -- Check if we don't want to ignore if ActorIgnoreTime > 0 then -- Initialize a timer to check the list of ignored monsters every half-second Timeout.add(500, function(ticks) -- Call the ignored monster check CheckIgnored(ticks) end, -- Repeatable = true true) else -- Replace the function that adds new ignores AddIgnore = NoReturn end -- Convert our MonsterDB table to be faster ConvertMDB() -- Initialize Advanced Movement Commands AMC_Init() end function mainAI(id) -- Log it Log(0,"Commencing post-AI-delay initialization...") -- Get us a temporary flag to see if we should replace AI() or mainAI() local replaceAI = false -- Default the next AI function to be AI_part2 local nextAI = AI_part2 -- Check if we're not debugging if Debug ~= true or type(DebugMark) ~= "number" or DebugMark < 1 then -- Replace the performance tracking replaceAI = true end -- Determine our owner's ID OwnerID = GetV(V_OWNER,id) Log(0,"Owner ID: %d", OwnerID) -- Determine information about our homunculus HomunID = id HomunRange = GetV(V_ATTACKRANGE,HomunID) HomunSpecificType = GetV(V_HOMUNTYPE,HomunID) -- Output info Log(0,"Homunculus ID: %d", HomunID) Log(0,"Homunculus Attack Range: %2.1f", HomunRange) -- Determine if the homunculus is advanced local advancedStr = "" if HomunType == AMISTR_H or HomunType == AMISTR_H2 or HomunType == FILIR_H or HomunType == FILIR_H2 or HomunType == LIF_H or HomunType == LIF_H2 or HomunType == VANILMIRTH_H or HomunType == VANILMIRTH_H2 then -- Its an advanced homunculus HomunAdvancedType = true -- Set the advanced string advancedStr = " High" -- eg, "Homunculus Type: High Vanilmirth" -- or, "Homunculus Type: Vanilmirth" end -- Determine the basic type of homunculus HomunType = HomunSpecificType if HomunType == AMISTR2 or HomunType == AMISTR_H or HomunType == AMISTR_H2 then HomunType = AMISTR elseif HomunType == FILIR2 or HomunType == FILIR_H or HomunType == FILIR_H2 then HomunType = FILIR elseif HomunType == LIF2 or HomunType == LIF_H or HomunType == LIF_H2 then HomunType = LIF elseif HomunType == VANILMIRTH2 or HomunType == VANILMIRTH_H or HomunType == VANILMIRTH_H2 then HomunType = VANILMIRTH end -- Initialize the script based on which homunculus we have if HomunType == AMISTR then Log(0,"Homunculus Type:" .. advancedStr .. " AMISTR") Amistr_Init() elseif HomunType == FILIR then Log(0,"Homunculus Type:" .. advancedStr .. " FILIR") Filir_Init() elseif HomunType == LIF then Log(0,"Homunculus Type:" .. advancedStr .. " LIF") Lif_Init() elseif HomunType == VANILMIRTH then Log(0,"Homunculus Type:" .. advancedStr .. " VANILMIRTH") Vanilmirth_Init() end -- Initialize the homunculus's skills InitHomunSkills() -- Initialize any customizations InitCustom() -- Check if we should assume we're in a PVP zone if AssumePVP then -- Assume we're in a pvp zone all the time PVPZone = true -- Initialize the PVP-zone specific custom addons InitCustomPVP() elseif AssumeWoE then -- Assume we're in a woe zone all the time PVPZone = true WoEZone = true -- Initialize the WoE-zone specific custom addons InitCustomWoE() elseif CheckPVP then -- Log it Log(0,"Auto-detecting PVP zone...") -- Set the next function to be a PVP-auto-detector nextAI = AI_pvp_check end -- Check if we should replace the AI() function (so we don't check performance) if replaceAI then AI = nextAI end -- We're done initializing, never do it again mainAI = nextAI -- Now that we're done initializing, call the main logic mainAI() end ------------------------ -- PVP Auto-detection -- ------------------------ --[[ NOTE: Go figure. As soon as I implement auto-PVP detection, GRAVITY makes it so that homunculi are no longer able to attack themselves. --]] function AI_pvp_check() -- Get the current ticks local ticks = GetTick() -- Get the current amount of HP/SP that we have local hp = GetV(V_HP,HomunID) local sp = GetV(V_SP,HomunID) -- Check if we count as a monster if IsMonster(HomunID) == 1 then -- Log it Log(0,"PVP zone detected!") -- We're in a PVP zone PVPZone = true -- Initialize customizations InitCustomPVP() -- Check if we're attacking ourself or we have >less< SP than we did -- before the PVP check elseif GetV(V_TARGET,HomunID) == HomunID or (PrePVPCheckSP ~= nil and sp < PrePVPCheckSP) then --[[ NOTE: If we did NOT show up as a monster, but we CAN attack ourself, then we must be in a War of Emperium zone. --]] -- Default to being in a WoE zone local foundWoE = true -- Check if we used SP if PrePVPCheckSP and sp < PrePVPCheckSP then -- Check if our HP is lower because of it if hp >= PrePVPCheckHP then -- WoE zone not detected. SP was used, but we didn't do any damage foundWoE = false end end -- Check if we still found WoE if foundWoE then -- Log it Log(0,"WoE zone detected!") -- We're in a PVP zone PVPZone = true WoEZone = true -- Initialize customizations InitCustomWoE() end end -- Check how long its been since we started if AIStartedAt + AIStartWait + 500 < ticks or PVPZone then -- We've waited long enough, set the next AI part -- Check if we haven't detected a PVP zone if not PVPZone then -- Log it Log(0,"PVP zone >NOT< detected!") end -- Check to see if the main AI is us if AI == AI_pvp_check then -- Set the next AI AI = AI_part2 end -- Set the main AI mainAI = AI_part2 -- Call the next AI return mainAI() end -- Default to not checking skills local skillCheck = false -- Check type of homunculus if HomunType == FILIR then -- Check if we have enough sp if sp >= 4 then -- Check WoE zone by using moonlight skillCheck = true end elseif HomunType == VANILMIRTH then -- Check if we have enough sp if sp >= 22 then -- Check WoE zone by using caprice skillCheck = true end end -- Check if we want to check skills if skillCheck then -- Save our HP/SP values PrePVPCheckSP = sp PrePVPCheckHP = hp -- Send a command to attack ourself SkillObject(HomunID,1,8009,HomunID) -- moonlight SkillObject(HomunID,1,8013,HomunID) -- caprice end -- Send a command to attack ourself Attack(HomunID,HomunID) end ------------------- -- メインAIロジック -- ------------------- function AI_part2() -- Get the command from the user interface local shift = false local msg = GetMsg(HomunID) -- Get our owner's motion (to be used later) OwnerMotion = GetOwnerMotion() -- Get our homunculus's motion HomunMotion = GetHomunMotion() -- If there is no message, check for a "reserved" message if msg[1] == NONE_CMD then shift = true msg = GetResMsg(HomunID) end -- Process the command if msg[1] ~= NONE_CMD then ProcessCommand(msg,shift) end -- Check the HP deltas CalculateHPDeltas() -- Gather information about each actor GetActorInfo() -- Check if its time that we try use a healing skill PauseAttackSkill = HomunSkillCheck() -- Process our timeouts Timeout.iterate() -- Process the sequence and grab the next one thats scheduled local seq = ProcessSequence() -- Check if we're doing anything local emptySeq = false if seq[1] == SEQ_NONE then -- Not doing anything, so we'll process sequence again at -- the end (in case we do want to start doing something -- this cycle) emptySeq = true end -- Check if we should start chasing our owner (xhint and yhint) local chase,distmult,xh,yh = CheckDistance(seq) if chase then -- If we're moving... if seq[1] == SEQ_MOVE or seq[1] == SEQ_MOVE_TO or seq[1] == SEQ_MOVE_TO_POS or seq[1] == SEQ_MOVE_TO_NEG or seq[1] == SEQ_PREROUTED then -- Then stop. (wasted command) --Move(HomunID,GetV(V_POSITION,HomunID)) -- And remove it from the stack too List.popleft(AI_seq) else -- Make a new sequence seq = { } end -- Reuse the seq we have seq[1] = SEQ_MOVE_TO seq[2] = OwnerID seq[3] = HomunFollowDistance * distmult seq[4] = xh seq[5] = yh seq.loglevel = 3 -- Call the sequencer's "move-to-actor" function local dummy dummy,seq = SEQ_AMove(seq) -- If the function wishes to be called again, -- push it onto the front of our AI_seq stack if seq ~= nil then List.pushleft(AI_seq,seq) end -- Do nothing else now (disabled) --return end -- Get the current tick count local ticks = GetTick() -- Acquire a target if ticks > AcquireTargetNextTime then -- Acquire a target for the homunculus HomuAcquireTarget() -- Acquire a target for the alchemist AlchAcquireTarget() -- Reset the timeout AcquireTargetNextTime = ticks + AcquireTargetTimeout end -- Check auto-alchemist skills CheckAlchSkills() -- Check for position lag CheckPosLag() -- Call customized AI CustomAI() -- Check if we're doing anything if List.size(AI_seq) < 1 then -- Check if we should be considered idle now if HomunLast.Sequence + TimeBeforeIdle < ticks then -- Do whatever the personality says to do while idle Personality_Idle() end else -- Set the last sequence HomunLast.Sequence = ticks end -- Check if our sequencer was empty originally, but we have something now if List.size(AI_seq) > 0 and emptySeq then -- Process the sequence again, to get started immediately ProcessSequence() end -- Save same information OwnerLast.Motion = OwnerMotion OwnerLast.X,OwnerLast.Y = GetV(V_POSITION,OwnerID) end ------------------------------- -- GetHomunMotion() function -- ------------------------------- function AI_HomunMotion() -- Get the motion local motion = GetV(V_MOTION,HomunID) --[[ Homunculus is never counted as "attacking" for some reason -- Check if the motion is an attack if motion == MOTION_ATTACK or motion == MOTION_ATTACK2 then -- Set the last time it attacked HomunLast.Attack = GetTick() end --]] return motion end -------------------------------- -- GetOwnerMotion() functions -- -------------------------------- function AI_GetOwnerMotion_Normal() local motion = GetV(V_MOTION,OwnerID) -- Check if the motion is an attack if motion == MOTION_ATTACK or motion == MOTION_ATTACK2 then -- Set the last time they attacked OwnerLast.Attack = GetTick() end -- Check if the motion is a movement if motion == MOTION_MOVE then OwnerLast.Move = GetTick() end -- Check if the motion is a pickup if motion == MOTION_PICKUP then OwnerLast.Pickup = GetTick() end -- Check if the motion is a sit if motion == MOTION_SIT then -- Check if we weren't already sitting if OwnerLast.Motion ~= MOTION_SIT then OwnerLast.Sit = GetTick() end end return motion end function AI_GetOwnerMotion_NoPhen() -- Get their motion local motion = AI_GetOwnerMotion_Normal() -- Check if they got hit if motion == MOTION_DAMAGE then -- Reset the casting time, we got hit OwnerSkillTimeout = 0 -- Return the motion return motion end -- Reuse the phen function return AI_GetOwnerMotion_Phen() end function AI_GetOwnerMotion_Phen() -- Get the motion local motion = AI_GetOwnerMotion_Normal() -- Check if we're casting if OwnerSkillTimeout ~= 0 then -- Check if its expired if OwnerSkillTimeout <= GetTick() then -- Its expired OwnerSkillTimeout = 0 else -- Not expired, they're casting motion = MOTION_CASTING end end -- Return their motion return motion end --------------------------- -- Follow/Lead Functions -- --------------------------- -- Function to decide if the homunculus is already chasing its owner function AI_CheckDistance_PreTest(seq) -- Default to no local ret = false -- Check if the sequence is a move-to our owner if (seq[1] == SEQ_MOVE_TO or seq[1] == SEQ_MOVE_TO_POS or seq[1] == SEQ_MOVE_TO_NEG) and seq[2] == OwnerID then -- We are return true end return false end -- Function to decide if a homunculus should start chasing its master (follow) function AI_CheckDistance_Follow(seq) -- Default to not checking the distance local ret = false -- Check if we're following our owner already if AI_CheckDistance_PreTest(seq) then -- No reason to start following again return false end -- Get the distance local dist if seq[1] == SEQ_MOVE then -- Get the block distance between our target location and owner dist = APBlocks(OwnerID,seq[2],seq[3]) elseif seq[1] == SEQ_MOVE_TO or seq[1] == SEQ_MOVE_TO_POS or seq[1] == SEQ_MOVE_TO_NEG or seq[1] == SEQ_PREROUTED then -- Get the block distance between our target and owner dist = AABlocks(seq[2],OwnerID) -- Check if its just following them if seq[1] == SEQ_MOVE_TO_POS and dist <= SightRange then dist = dist - seq[3] end elseif seq[1] == SEQ_ATTACK then -- Get the block distance between our target monster and owner dist = AABlocks(Target.ID,OwnerID) -- Check for a special case if dist > MaxHomunRange and dist <= SightRange then -- Its not pursuing them, don't run to owner return false,1 end elseif seq[1] == SEQ_KITE then -- Get the block distance between our owner and monster dist = AABlocks(Target.ID,OwnerID) else -- Get the block distance between our homun and owner dist = AABlocks(HomunID,OwnerID) end -- Range to our owner local range = MaxHomunRange -- Check if our owner is moving if OwnerMotion == MOTION_MOVE then -- Owner is moving -- Check if our owner is moving away from us/our target if dist > OwnerLast.Dist then -- Shorten the range range = range - HomunRangeChange end -- Check if we were told to sit a a specific place if HoldPosition == true and dist > range then -- Clear the hold position flag HoldPosition = false end end -- Check if we should start chasing our owner if HoldPosition == false and dist > range then -- We're going to have to run to our owner ret = true end -- Save the owner's distance for this cycle OwnerLast.Dist = dist -- return ret,1 end -- Function to decide if a homunculus should run ahead of its master (lead) function AI_CheckDistance_Lead(seq) -- Check if we're already leading if AI_CheckDistance_PreTest(seq) then -- Don't do another lead command return false end -- Get old distance local olddist = OwnerLast.Dist -- First and foremost, check if we need to follow if AI_CheckDistance_Follow(seq) then return true,1 end -- Get the owner's old/new X and Y local x,y = GetV(V_POSITION,OwnerID) -- Check if we're holding position if HoldPosition == true then -- Don't chase our owner return false end -- Check if we're in the middle of fighting if seq[1] == SEQ_ATTACK then -- Don't chase our owner return false end -- Check if our owner has stood still if OwnerLast.X == 0 or (OwnerLast.X == x and OwnerLast.Y == y) then -- Don't chase, they're standing still return false end -- Regular with hints return true,-1,OwnerLast.X,OwnerLast.Y end ----------------------- -- シミュレータサポート -- ----------------------- --[[ シミュレータモードならチェックすること (Simulator.luaを読めばより 情報があります) 注意: Lua は上から下へ実行します。 ROはホムンクルススクリプトを 開始する必要があるとき、AI.luaを実行し、("require" 命令文とともに 一方を実行して戻ります) そしてそれからROは関数を繰り返して走らせ 続けます。決まった状況に対するスクリプトをテストするのを助ける 関数を書いて以来、もし現にROクライアントか、またはそうでないならば 決定するために呼ぶこの関数を使います。 注意: また、ROクライアントでないなら、CheckSimulation()はSimulator.lua で定義されたテストケースに対するスクリプトを実行します。 --]] CheckSimulation()