mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 08:39:11 +00:00
Add support for Events defined in Ruby
A slightly complicated and hacky change, because this requires working around JRuby issue 2359.
This commit is contained in:
+46
-10
@@ -18,6 +18,9 @@ java_import 'org.apollo.game.model.World'
|
||||
java_import 'org.apollo.game.model.entity.Player'
|
||||
java_import 'org.apollo.game.model.event.EventListener'
|
||||
java_import 'org.apollo.game.model.event.PlayerEvent'
|
||||
java_import 'org.apollo.game.model.event.impl.LoginEvent'
|
||||
java_import 'org.apollo.game.model.event.ProxyEvent'
|
||||
java_import 'org.apollo.game.model.event.ProxyEventListener'
|
||||
java_import 'org.apollo.game.model.entity.setting.PrivilegeLevel'
|
||||
java_import 'org.apollo.game.scheduling.ScheduledTask'
|
||||
java_import 'org.apollo.game.plugin.PluginContext'
|
||||
@@ -129,23 +132,26 @@ def schedule(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Defines some sort of action to take upon an message. The following types of
|
||||
# message are currently valid:
|
||||
@@proxy_listener = ProxyEventListener.new
|
||||
$world.listen_for(ProxyEvent.java_class, @@proxy_listener)
|
||||
|
||||
# Defines some sort of action to take upon an message. The following types of message are currently
|
||||
# valid:
|
||||
#
|
||||
# * :command
|
||||
# * :message
|
||||
# * :button
|
||||
# * Any valid Event, as a symbol in ruby snake_case form.
|
||||
#
|
||||
# A command takes one or two arguments (the command name and optionally the
|
||||
# minimum rights level to use it). The minimum rights level defaults to
|
||||
# STANDARD. The block should have two arguments: player and command.
|
||||
# A command takes one or two arguments (the command name and optionally the minimum rights level to
|
||||
# use it). The minimum rights level defaults to STANDARD. The block should have two arguments:
|
||||
# player and command.
|
||||
#
|
||||
# An message takes no arguments. The block should have three arguments: the chain
|
||||
# context, the player and the message object.
|
||||
# An message takes no arguments. The block should have two arguments: the player and the message
|
||||
# object.
|
||||
#
|
||||
# A button takes one argument (the id). The block should have one argument: the
|
||||
# player who clicked the button.
|
||||
# A button takes one argument (the id). The block should have one argument: the player who clicked
|
||||
# the button.
|
||||
def on(type, *args, &block)
|
||||
case type
|
||||
when :command then on_command(args, block)
|
||||
@@ -153,11 +159,41 @@ def on(type, *args, &block)
|
||||
when :button then on_button(args, block)
|
||||
else
|
||||
class_name = type.to_s.camelize.concat('Event')
|
||||
type = Java::JavaClass.for_name("org.apollo.game.model.event.impl.#{class_name}")
|
||||
|
||||
begin
|
||||
type = Java::JavaClass.for_name("org.apollo.game.model.event.impl.#{class_name}")
|
||||
rescue
|
||||
@@proxy_listener.add(class_name, ProcEventListener.new(block))
|
||||
return
|
||||
end
|
||||
|
||||
$world.listen_for(type, ProcEventListener.new(block))
|
||||
end
|
||||
end
|
||||
|
||||
# Contains extension methods for World.
|
||||
module WorldExtensions
|
||||
|
||||
# Overrides World#submit, providing special-case behaviour for Events defined in Ruby, which
|
||||
# need to be wrapped in a ProxyEvent, until https://github.com/jruby/jruby/issues/2359 is
|
||||
# resolved.
|
||||
def submit(event)
|
||||
if event.java_class.name.end_with?(".Event", ".PlayerEvent")
|
||||
event = ProxyEvent.new(event.class.name, event)
|
||||
end
|
||||
|
||||
super(event)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Prepend the methods defined in WorldExtensions to World.
|
||||
class World
|
||||
prepend WorldExtensions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Defines an action to be taken upon a button press.
|
||||
def on_button(args, proc)
|
||||
fail 'Button must have one argument.' unless args.length == 1
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
|
||||
java_import 'org.apollo.game.action.DistancedAction'
|
||||
java_import 'org.apollo.game.model.event.Event'
|
||||
|
||||
private
|
||||
|
||||
# A distanced action which opens a door.
|
||||
class OpenDoorAction < DistancedAction
|
||||
@@ -24,10 +27,29 @@ class OpenDoorAction < DistancedAction
|
||||
|
||||
end
|
||||
|
||||
# MessageListener for opening and closing doors.
|
||||
# A PlayerEvent that is fired when a player attempts to open a door.
|
||||
class OpenDoorEvent < PlayerEvent
|
||||
attr_reader :door
|
||||
|
||||
def initialize(player, door)
|
||||
super(player)
|
||||
@door = door
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Listens for FirstObjectActions performed on doors.
|
||||
on :message, :first_object_action do |player, message|
|
||||
if DoorUtil.door?(message.id)
|
||||
door = DoorUtil.get_door_object(message.position, message.id)
|
||||
player.start_action(OpenDoorAction.new(player, door)) unless door.nil?
|
||||
id = message.id
|
||||
|
||||
if DoorUtil.door?(id)
|
||||
position = message.position
|
||||
door = DoorUtil.get_door_object(position, id)
|
||||
|
||||
if !door.nil? && $world.submit(OpenDoorEvent.new(player, door))
|
||||
player.start_action(OpenDoorAction.new(player, door))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apollo.Service;
|
||||
import org.apollo.cache.IndexedFileSystem;
|
||||
import org.apollo.cache.decoder.ItemDefinitionDecoder;
|
||||
@@ -31,8 +32,6 @@ import org.apollo.game.scheduling.Scheduler;
|
||||
import org.apollo.game.scheduling.impl.NpcMovementTask;
|
||||
import org.apollo.util.NameUtil;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* The world class is a singleton which contains objects like the {@link MobRepository} for players and NPCs. It should
|
||||
* only contain things relevant to the in-game world and not classes which deal with I/O and such (these may be better
|
||||
@@ -86,6 +85,11 @@ public final class World {
|
||||
*/
|
||||
private final MobRepository<Npc> npcRepository = new MobRepository<>(WorldConstants.MAXIMUM_NPCS);
|
||||
|
||||
/**
|
||||
* The Queue of Npcs that have yet to be removed from the repository.
|
||||
*/
|
||||
private final Queue<Npc> oldNpcs = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* The {@link MobRepository} of {@link Player}s.
|
||||
*/
|
||||
@@ -100,11 +104,6 @@ public final class World {
|
||||
* The Queue of Npcs that have yet to be added to the repository.
|
||||
*/
|
||||
private final Queue<Npc> queuedNpcs = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* The Queue of Npcs that have yet to be removed from the repository.
|
||||
*/
|
||||
private final Queue<Npc> oldNpcs = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* This world's {@link RegionRepository}.
|
||||
@@ -209,8 +208,8 @@ public final class World {
|
||||
releaseNumber = release;
|
||||
|
||||
SynchronousDecoder decoder = new SynchronousDecoder(new ItemDefinitionDecoder(fs),
|
||||
new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
|
||||
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
|
||||
new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
|
||||
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
|
||||
|
||||
decoder.block();
|
||||
|
||||
@@ -315,8 +314,7 @@ public final class World {
|
||||
* @param npc The npc.
|
||||
*/
|
||||
public void unregister(final Npc npc) {
|
||||
Preconditions.checkNotNull(npc, "Npc may not be null.");
|
||||
|
||||
Preconditions.checkNotNull(npc, "Npc must not be null.");
|
||||
oldNpcs.add(npc);
|
||||
}
|
||||
|
||||
@@ -362,7 +360,7 @@ public final class World {
|
||||
npcMovement.addNpc(npc);
|
||||
}
|
||||
} else {
|
||||
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
|
||||
logger.warning("Failed to register npc (capacity reached): [count=" + npcRepository.size() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,7 +371,7 @@ public final class World {
|
||||
private void unregisterNpcs() {
|
||||
while (!oldNpcs.isEmpty()) {
|
||||
Npc npc = oldNpcs.poll();
|
||||
|
||||
|
||||
Region region = regions.fromPosition(npc.getPosition());
|
||||
region.removeEntity(npc);
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.apollo.game.model.event;
|
||||
|
||||
/**
|
||||
* An {@link Event} that wraps another {@link Event}.
|
||||
*
|
||||
* This is a workaround for JRuby issue <a href="https://github.com/jruby/jruby/issues/2359">2359</a>. This class
|
||||
* should <strong>not</strong> be used in Java.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class ProxyEvent extends Event {
|
||||
|
||||
/**
|
||||
* The Event created by a Ruby plugin.
|
||||
*/
|
||||
private final Event event;
|
||||
|
||||
/**
|
||||
* The name of the Ruby Event.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Creates the ProxyEvent.
|
||||
*
|
||||
* @param name The name of the {@link Event} defined in Ruby.
|
||||
* @param event The Event created by a Ruby plugin.
|
||||
*/
|
||||
public ProxyEvent(String name, Event event) {
|
||||
this.name = name;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the {@link Event} defined in Ruby.
|
||||
*
|
||||
* @return The name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Event} created in a Ruby plugin.
|
||||
*
|
||||
* @return The Ruby {@link Event}.
|
||||
*/
|
||||
public Event getRuby() {
|
||||
return event;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.apollo.game.model.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An {@link EventListener} for {@link ProxyEvent}s.
|
||||
*
|
||||
* This is a workaround for JRuby issue <a href="https://github.com/jruby/jruby/issues/2359">2359</a>.
|
||||
*
|
||||
* @author Major
|
||||
*/
|
||||
public final class ProxyEventListener implements EventListener<ProxyEvent> {
|
||||
|
||||
/**
|
||||
* The {@link Map} from Event names to {@link List}s of {@link EventListener}s.
|
||||
*/
|
||||
private final Map<String, List<EventListener<Event>>> listeners = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Registers an {@link EventListener} to this proxy. This method is <strong>not</strong> type-safe, and must only
|
||||
* be called from ruby.
|
||||
*
|
||||
* @param name The name of the Event. Must not be {@code null}.
|
||||
* @param listener The {@link EventListener} to add. Must not be {@code null}.
|
||||
*/
|
||||
public void add(String name, EventListener<Event> listener) {
|
||||
List<EventListener<Event>> listeners = this.listeners.computeIfAbsent(name, n -> new ArrayList<>(2));
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handle(ProxyEvent event) {
|
||||
List<EventListener<Event>> chain = listeners.get(event.getName());
|
||||
|
||||
if (chain != null) {
|
||||
for (EventListener<Event> listener : chain) {
|
||||
Event ruby = event.getRuby();
|
||||
listener.handle(ruby);
|
||||
|
||||
if (ruby.terminated()) {
|
||||
event.terminate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,11 +20,6 @@ public final class RubyPluginEnvironment implements PluginEnvironment {
|
||||
*/
|
||||
private final ScriptingContainer container = new ScriptingContainer();
|
||||
|
||||
/**
|
||||
* The World this RubyPluginEnvironment is for.
|
||||
*/
|
||||
private final World world;
|
||||
|
||||
/**
|
||||
* Creates and bootstraps the Ruby plugin environment.
|
||||
*
|
||||
@@ -32,7 +27,7 @@ public final class RubyPluginEnvironment implements PluginEnvironment {
|
||||
* @throws IOException If an I/O error occurs during bootstrapping.
|
||||
*/
|
||||
public RubyPluginEnvironment(World world) throws IOException {
|
||||
this.world = world;
|
||||
container.put("$world", world);
|
||||
parseBootstrapper();
|
||||
}
|
||||
|
||||
@@ -42,7 +37,7 @@ public final class RubyPluginEnvironment implements PluginEnvironment {
|
||||
container.runScriptlet(is, name);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Error parsing scriptlet " + name + ".");
|
||||
throw new RuntimeException("Error parsing scriptlet " + name + ".", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +56,6 @@ public final class RubyPluginEnvironment implements PluginEnvironment {
|
||||
@Override
|
||||
public void setContext(PluginContext context) {
|
||||
container.put("$ctx", context);
|
||||
container.put("$world", world);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user