11/*
2- This file is part of the OdinMS Maple Story Server
3- Copyright (C) 2008 Patrick Huy <[email protected] > 4- 5- Jan Christian Meyer <[email protected] > 6-
7- This program is free software: you can redistribute it and/or modify
8- it under the terms of the GNU Affero General Public License as
9- published by the Free Software Foundation version 3 as published by
10- the Free Software Foundation. You may not use, modify or distribute
11- this program under any other version of the GNU Affero General Public
12- License.
13-
14- This program is distributed in the hope that it will be useful,
15- but WITHOUT ANY WARRANTY; without even the implied warranty of
16- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17- GNU Affero General Public License for more details.
18-
19- You should have received a copy of the GNU Affero General Public License
20- along with this program. If not, see <http://www.gnu.org/licenses/>.
21- */
22-
23- const Point = Java . type ( 'java.awt.Point' ) ;
2+ * 通用野外BOSS事件脚本模板 (Standalone Area Boss Script Template)
3+ *
4+ * 功能:
5+ * 1. 自动生成指定 BOSS。
6+ * 2. 监听 BOSS 死亡事件。
7+ * 3. 死亡后依指定时间准确重置刷新。
8+ * 4. [新增] 只有当玩家进入地图且冷却时间已到时才生成 (Lazy Spawn)。
9+ *
10+ * 使用说明:
11+ * - 自动更新脚本生成
12+ */
13+
14+ // ============================================================================
15+ // 配置区域 (Configuration)
16+ // ============================================================================
17+
18+ // 地图 ID (BOSS 生成的地图)
19+ const MapID = 800020120 ;
20+
21+ // BOSS ID (要生成的怪物 ID)
22+ const BossID = 6090002 ;
23+
24+ // BOSS 名称 (默认名称,若数据库有读取到会自动更新)
25+ var BossName = "青竹武士" ;
26+
27+ // 刷新冷却时间 (单位:分钟)
28+ const BossTime = 180 ;
29+
30+ // BOSS 生成坐标 (X, Y)
31+ const SpawnPoint = new java . awt . Point ( Math . floor ( ( Math . random ( ) * 100 ) + 600 ) , 50 ) ;
32+
33+ // BOSS 出现时的全服公告内容
34+ const BossNotice = "青岚自氤氲雾气笼罩的断垣残壁间,骤然现形!" ;
35+
36+ // ============================================================================
37+ // 核心逻辑 (Core Logic)
38+ // ============================================================================
39+
2440const LifeFactory = Java . type ( 'org.gms.server.life.LifeFactory' ) ;
2541const PacketCreator = Java . type ( 'org.gms.util.PacketCreator' ) ;
2642const LoggerFactory = Java . type ( 'org.slf4j.LoggerFactory' ) ;
43+
2744var log = null ;
2845var channel = null ;
29- var isinit = false ;
3046
31- var MapID = 800020120 ;
32- var BossID = 6090002 ;
33- var BossName = "青竹武士" ;
34- /**刷新时间,分钟; Generation time in minutes*/
35- var BossTime = 180 ;
36- /**指定Boss刷新的XY坐标位置; Specify the XY coordinate position for Boss refresh*/
37- var point = new Point ( Math . floor ( ( Math . random ( ) * 100 ) + 600 ) , 50 ) ;
38- var BossNotice = "青岚自氤氲雾气笼罩的断垣残壁间,骤然现形!" ;
47+ // 事件实例名称 (自动获取,无需修改)
48+ const EventName = "AreaBossBamboo" ;
3949
40- const methodName = "start" ; //指定当前事件刷新Boss的函数,无需改动
41- /**
42- -- Odin JavaScript --------------------------------------------------------------------------------
43- Zeno Spawner
44- -- Edited by --------------------------------------------------------------------------------------
45- ThreeStep - based on xQuasar's King Clang spawner
46- **/
4750function init ( ) {
4851 channel = em . getChannelServer ( ) . getId ( ) ;
4952 log = LoggerFactory . getLogger ( em . getName ( ) ) ;
50-
51- scheduleNew ( ) ;
53+ em . registerMapListener ( MapID ) ;
54+ scheduleNew ( ) ; // [新系统] 注册地图监听器,避免绑定玩家 EIM
5255}
5356
5457function scheduleNew ( ) {
55- setupTask = em . schedule ( methodName , 0 ) ; //服务器启动时生成。每指定时间,服务器事件会检查boss是否存在,如果不存在,会立即生成boss。
58+ setupEventInstance ( ) ;
59+
60+ var eim = getOrCreateEventInstance ( ) ;
61+ // 标记:冷却已就绪 (服务器重启默认为就绪)
62+ eim . setProperty ( "canSpawn" , "true" ) ;
63+
64+ // 关键修正:必须启动事件,monsterKilled 等监听器才会生效
65+ try { eim . startEvent ( ) ; } catch ( e ) { }
66+
67+ // 尝试生成
68+ // 如果地图已经有人,立即生成
69+ // 如果地图没人,不做任何事,等待玩家进入
70+ checkAndSpawn ( eim ) ;
5671}
5772
5873function cancelSchedule ( ) {
59- if ( setupTask != null ) {
60- setupTask . cancel ( true ) ;
74+ var eim = em . getInstance ( EventName ) ;
75+ if ( eim != null ) {
76+ eim . dispose ( ) ;
6177 }
6278}
6379
64- function start ( ) {
65- var graysPrairie = em . getChannelServer ( ) . getMapFactory ( ) . getMap ( MapID ) ;
66- var Timer = em . getBossTime ( BossTime * 60 * 1000 ) ; //转为毫秒并加载时间倍率修正
80+ // 辅助函数:获取或创建实例
81+ function getOrCreateEventInstance ( ) {
82+ var eim = em . getInstance ( EventName ) ;
83+ if ( eim == null ) {
84+ try {
85+ eim = em . newInstance ( EventName ) ;
86+ } catch ( e ) {
87+ eim = em . getInstance ( EventName ) ;
88+ }
89+ }
90+ return eim ;
91+ }
92+
93+ function setupEventInstance ( ) {
94+ var eim = getOrCreateEventInstance ( ) ;
95+ var map = em . getChannelServer ( ) . getMapFactory ( ) . getMap ( MapID ) ;
96+ // 绑定地图到EIM
97+ map . setEventInstance ( eim ) ;
98+ }
99+
100+ // 核心监测函数:替代 playerEntry 的轮询机制
101+ function checkAndSpawn ( eim ) {
102+ if ( eim == null ) return ;
67103
68- if ( graysPrairie . getMonsterById ( BossID ) != null ) {
69- em . schedule ( methodName , Timer ) ;
104+ var canSpawn = eim . getProperty ( "canSpawn" ) ;
105+ // 如果不能刷(已经刷了,或正在CD),直接停止监测
106+ if ( canSpawn != "true" ) {
70107 return ;
71108 }
72- const BossObj = LifeFactory . getMonster ( BossID ) ;
73- BossName = BossObj . getName ( ) || BossName ;
109+
110+ var map = em . getChannelServer ( ) . getMapFactory ( ) . getMap ( MapID ) ;
111+
112+ // 1. 检查人数
113+ if ( map . getCharacters ( ) . size ( ) > 0 ) {
114+ // 有人,嘗試生成
115+ spawnBoss ( eim , map ) ;
116+ }
117+ }
118+
119+
120+ function spawnBoss ( eim , map ) {
74121 try {
75- graysPrairie . spawnMonsterOnGroundBelow ( BossObj , point ) ;
76- if ( isinit ) {
77- log . info ( `[事件脚本-野外BOSS] ${ em . getName ( ) } 已在频道 ${ channel } 的 ${ graysPrairie . getMapName ( ) } (${ MapID } ) ${ point . x } , ${ point . y } ) 生成 ${ BossName } (${ BossID } ),检测间隔:${ Timer / 60 / 1000 } 分钟` ) ;
78- } else {
79- isinit = true ;
122+ // 双重保险:检查 BOSS 是否已存在
123+ if ( map . getMonsterById ( BossID ) != null ) {
124+ eim . setProperty ( "canSpawn" , "false" ) ;
125+ return ;
80126 }
81- } catch ( e ) {
82- console . error ( `[事件脚本-野外BOSS] ${ em . getName ( ) } 在频道 ${ channel } 的 ${ graysPrairie . getMapName ( ) } (${ MapID } ) ${ point . x } , ${ point . y } ) 生成 ${ BossName } (${ BossID } ) 时出错` , e ) ;
83- }
84- graysPrairie . broadcastMessage ( PacketCreator . serverNotice ( 6 , `[野外BOSS] ${ BossName } ${ BossNotice } ` ) ) ; //聊天框输出当前地图范围的Boss登场消息
85127
86- em . schedule ( methodName , Timer ) ;
87- }
128+ // 准备 BOSS 对象
129+ var bossMob = LifeFactory . getMonster ( BossID ) ;
130+ if ( bossMob . getName ( ) ) {
131+ BossName = bossMob . getName ( ) ;
132+ }
88133
89- // ---------- FILLER FUNCTIONS ----------
134+ // 生成 BOSS
135+ map . spawnMonsterOnGroundBelow ( bossMob , SpawnPoint ) ;
90136
91- function dispose ( ) { }
137+ // 标记为不可刷新
138+ eim . setProperty ( "canSpawn" , "false" ) ;
92139
93- function setup ( eim , leaderid ) { }
94140
95- function monsterValue ( eim , mobid ) { return 0 ; }
96141
97- function disbandParty ( eim , player ) { }
142+ // 发送公告
143+ map . broadcastMessage ( PacketCreator . serverNotice ( 6 , `[野外BOSS] ${ BossName } ${ BossNotice } ` ) ) ;
98144
99- function playerDisconnected ( eim , player ) { }
145+ } catch ( e ) {
146+ if ( log ) log . error ( `[野外BOSS] ${ EventName } 生成失败` , e ) ;
147+ }
148+ }
100149
101- function playerEntry ( eim , player ) { }
150+ // ============================================================================
151+ // 事件回调 (Event Hooks)
152+ // ============================================================================
102153
103- function monsterKilled ( mob , eim ) { }
154+ /**
155+ * [新系统] 当玩家进入监听地图时触发
156+ */
157+ function onMapPlayerEnter ( em , player , mapId ) {
158+ if ( mapId != MapID ) return ;
104159
105- function scheduledTimeout ( eim ) { }
160+ var eim = em . getInstance ( EventName ) ;
161+ if ( eim != null ) {
106162
107- function afterSetup ( eim ) { }
163+ checkAndSpawn ( eim ) ;
164+ }
165+ }
108166
109- function changedLeader ( eim , leader ) { }
167+ function monsterKilled ( mob , eim ) {
168+ // 判断死亡的怪物是否为目标 BOSS
169+ if ( mob . getId ( ) == BossID ) {
170+ var nextRespawnTime = em . getBossTime ( BossTime * 60 * 1000 ) ;
110171
111- function playerExit ( eim , player ) { }
172+ if ( log ) log . info ( `[野外BOSS] ${ BossName } ( ${ BossID } ) 被击杀, ${ nextRespawnTime / 60000 } 分钟后刷新` ) ;
112173
113- function leftParty ( eim , player ) { }
174+ // 设定倒数计时,时间到后执行 cooldownFinished
175+ em . schedule ( "cooldownFinished" , nextRespawnTime ) ;
176+ }
177+ }
114178
115- function clearPQ ( eim ) { }
179+ // 计时器回调:冷却结束
180+ function cooldownFinished ( ) {
181+ var eim = getOrCreateEventInstance ( ) ;
182+ // 标记冷却结束
183+ eim . setProperty ( "canSpawn" , "true" ) ;
116184
117- function allMonstersDead ( eim ) { }
185+ // 启动监测
186+
187+ checkAndSpawn ( eim ) ;
188+ }
118189
119- function playerUnregistered ( eim , player ) { }
190+ // 必须保留的空函数
191+ function playerEntry ( eim , player ) { }
192+ function dispose ( ) { }
193+ function setup ( eim , leaderid ) { }
194+ function monsterValue ( eim , mobid ) { return 0 ; }
195+ function disbandParty ( eim , player ) { }
196+ function playerDisconnected ( eim , player ) { }
197+ function scheduledTimeout ( eim ) { }
198+ function afterSetup ( eim ) { }
199+ function changedLeader ( eim , leader ) { }
200+ function playerExit ( eim , player ) { }
201+ function leftParty ( eim , player ) { }
202+ function clearPQ ( eim ) { }
203+ function allMonstersDead ( eim ) { }
204+ function playerUnregistered ( eim , player ) { }
205+ function playerRevive ( eim , player ) { return true ; }
0 commit comments