mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-05 16:49:04 +00:00
Housekeeping
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
require 'java'
|
||||
|
||||
java_import 'org.apollo.game.model.entity.Mob'
|
||||
java_import 'org.apollo.game.model.entity.Npc'
|
||||
java_import 'org.apollo.game.model.entity.Player'
|
||||
java_import 'org.apollo.game.model.entity.attr.Attribute'
|
||||
java_import 'org.apollo.game.model.entity.attr.AttributeDefinition'
|
||||
java_import 'org.apollo.game.model.entity.attr.AttributeMap'
|
||||
java_import 'org.apollo.game.model.entity.attr.AttributePersistence'
|
||||
java_import 'org.apollo.game.model.entity.attr.AttributeType'
|
||||
java_import 'org.apollo.game.model.entity.attr.BooleanAttribute'
|
||||
java_import 'org.apollo.game.model.entity.attr.NumericalAttribute'
|
||||
java_import 'org.apollo.game.model.entity.attr.StringAttribute'
|
||||
|
||||
# Declares an attribute and adds its definition.
|
||||
def declare_attribute(name, default, persistence = :transient)
|
||||
if Player.method_defined?(name) || Mob.method_defined?(name) || Npc.method_defined?(name)
|
||||
fail "Attribute #{name} clashes with an existing variable."
|
||||
end
|
||||
|
||||
definition = AttributeDefinition.new(default, get_persistence(persistence), get_type(default))
|
||||
AttributeMap::define(name.to_s, definition)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The existing Mob class.
|
||||
class Mob
|
||||
|
||||
# Overrides method_missing to implement the functionality.
|
||||
def method_missing(symbol, *args)
|
||||
name = symbol.to_s.strip
|
||||
|
||||
if name[-1] == '='
|
||||
fail "Expected argument count of 1, received #{args.length}" unless args.length == 1
|
||||
|
||||
name = name[0...-1].strip # Drop the equals and trim whitespace.
|
||||
set_attribute(name, to_attribute(args[0]))
|
||||
elsif AttributeMap::get_definition(name).nil?
|
||||
super(symbol, *args)
|
||||
else
|
||||
attribute = get_attribute(name)
|
||||
value = attribute.value
|
||||
(attribute.type == AttributeType::SYMBOL) ? value.to_sym : value
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Gets the appropriate attribute for the specified value.
|
||||
def to_attribute(value)
|
||||
case value
|
||||
when String, Symbol then return StringAttribute.new(value.to_s, value.is_a?(Symbol))
|
||||
when Integer, Float then return NumericalAttribute.new(value)
|
||||
when TrueClass, FalseClass then return BooleanAttribute.new(value)
|
||||
else fail "Undefined attribute type #{value.class}."
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the attribute type of the specified value.
|
||||
def get_type(value)
|
||||
case value
|
||||
when String then return AttributeType::STRING
|
||||
when Symbol then return AttributeType::SYMBOL
|
||||
when Integer then return AttributeType::LONG
|
||||
when Float then return AttributeType::DOUBLE
|
||||
when TrueClass, FalseClass then return AttributeType::BOOLEAN
|
||||
else fail "Undefined attribute type #{value.class}."
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the Persistence type of the specified value.
|
||||
def get_persistence(persistence)
|
||||
unless [:persistent, :transient].include?(persistence)
|
||||
fail "Undefined persistence type #{persistence}."
|
||||
end
|
||||
|
||||
(persistence == :persistent) ? AttributePersistence::PERSISTENT : AttributePersistence::TRANSIENT
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<plugin>
|
||||
<id>attributes</id>
|
||||
<version>0.9</version>
|
||||
<name>Attributes</name>
|
||||
<description>Adds an attribute system for entites.</description>
|
||||
<authors>
|
||||
<author>Major</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>attributes.rb</script>
|
||||
</scripts>
|
||||
<dependencies />
|
||||
</plugin>
|
||||
@@ -0,0 +1,20 @@
|
||||
require 'java'
|
||||
|
||||
java_import 'org.apollo.game.model.entity.Mob'
|
||||
|
||||
module MobExtension
|
||||
MOB_EXTENSIONS = []
|
||||
|
||||
def self.register(extension)
|
||||
fail 'Provided extension object is not a module' unless extension.is_a?(Module)
|
||||
|
||||
new_mixins = extension.public_instance_methods
|
||||
current_mixins = MOB_EXTENSIONS.map { |e| {e.to_s => e.public_instance_methods} }
|
||||
|
||||
current_mixins.each do |ext, methods|
|
||||
methods.each {|m| fail "Extension #{ext} already provides method #{m}" if new_mixins.include?(m) }
|
||||
end
|
||||
|
||||
Mob.include(extension)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0"?>
|
||||
<plugin>
|
||||
<id>mob-extension</id>
|
||||
<version>0.1</version>
|
||||
<name>Mob Extensions</name>
|
||||
<description>Adds support for extending Mobs with new functionality</description>
|
||||
<authors>
|
||||
<author>Gary Tierney</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>extension.rb</script>
|
||||
</scripts>
|
||||
<dependencies>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
on :message, :walk do |player, msg|
|
||||
player.reset_interacting_mob
|
||||
end
|
||||
|
||||
on :message, :player_action do |player, msg|
|
||||
## todo: need a better way of mapping option numbers to their purpose
|
||||
player.follow($world.player_repository.get(msg.index)) if msg.option == 3
|
||||
end
|
||||
|
||||
##
|
||||
# A <code>MobExtension</code> for making a <code>Mob</code> trail behind, or chase a given mob.
|
||||
module FollowingMobExtension
|
||||
##
|
||||
# Follow a mob and trail behind them.
|
||||
def follow(mob)
|
||||
do_follow(self, mob, behind: true)
|
||||
end
|
||||
|
||||
##
|
||||
# Chase a mob (with the intention of getting in front of them), also optionally
|
||||
# stopping within a projectile distance when at a position where a projectile can
|
||||
# reach the target.
|
||||
def chase(mob, projectile_distance: nil)
|
||||
do_follow(self, mob, front: true, projectile_distance: projectile_distance)
|
||||
end
|
||||
end
|
||||
|
||||
MobExtension::register(FollowingMobExtension)
|
||||
|
||||
private
|
||||
|
||||
def do_follow(source, mob, behind: false, front: false, projectile_distance: nil)
|
||||
source.interacting_mob = mob
|
||||
|
||||
schedule 0, true do |task|
|
||||
# stop the task unless we're still interacting with the other mob.
|
||||
unless self.interacting_mob.eql? mob
|
||||
task.stop
|
||||
next
|
||||
end
|
||||
|
||||
next unless source.walking_queue.size <= 1
|
||||
|
||||
distance = mob.position.get_distance(source.position)
|
||||
|
||||
unless projectile_distance.nil?
|
||||
next if distance <= projectile_distance &&
|
||||
$world.collision_manager.raycast(source.position, mob.position)
|
||||
end
|
||||
|
||||
if distance > 15
|
||||
reset_interacting_mob
|
||||
end
|
||||
|
||||
walk_to(mob, behind: behind, front: front)
|
||||
end
|
||||
end
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<plugin>
|
||||
<id>mob-following</id>
|
||||
<version>1</version>
|
||||
<name>Following</name>
|
||||
<description>Adds following for mobs.</description>
|
||||
<authors>
|
||||
<author>Steve Soltys</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>following.rb</script>
|
||||
</scripts>
|
||||
<dependencies>
|
||||
<dependency>mob-walk-to</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<plugin>
|
||||
<id>mob-walk-to</id>
|
||||
<version>0.1</version>
|
||||
<name>Mob path generation</name>
|
||||
<description>Adds a mixin for making mobs walk to another entity.</description>
|
||||
<authors>
|
||||
<author>Gary Tierney</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>walk_to.rb</script>
|
||||
</scripts>
|
||||
<dependencies>
|
||||
<dependency>mob-extension</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
@@ -0,0 +1,83 @@
|
||||
java_import 'org.apollo.game.model.entity.path.AStarPathfindingAlgorithm'
|
||||
java_import 'org.apollo.game.model.entity.path.EuclideanHeuristic'
|
||||
java_import 'org.apollo.game.model.entity.path.SimplePathfindingAlgorithm'
|
||||
java_import 'org.apollo.game.model.entity.obj.GameObject'
|
||||
java_import 'org.apollo.game.model.entity.Mob'
|
||||
java_import 'org.apollo.game.model.entity.Player'
|
||||
java_import 'org.apollo.game.model.entity.EntityType'
|
||||
java_import 'org.apollo.game.model.Direction'
|
||||
|
||||
##
|
||||
# A pathfinder used for simple following (i.e.: An NPC attacking a player).
|
||||
SIMPLE_PATH_FINDER = SimplePathfindingAlgorithm.new($world.collision_manager)
|
||||
|
||||
##
|
||||
# A pathfinder used for precise following (i.e.: A player attacking another player).
|
||||
FOLLOW_PATH_FINDER = AStarPathfindingAlgorithm.new($world.collision_manager, EuclideanHeuristic.new)
|
||||
|
||||
##
|
||||
# The directions that we use as a random offset when not walking to the facing direction.
|
||||
OFFSET_DIRECTIONS = Direction::NESW.to_a
|
||||
|
||||
module WalkToMobExtension
|
||||
def walk_to(entity, front: false, behind: false)
|
||||
if [front, behind].count { |option| option == true } > 1
|
||||
fail 'Can only specify one of "front" or "behind"'
|
||||
end
|
||||
|
||||
position = self.position
|
||||
target_position = entity.position
|
||||
target_distance = position.get_distance(target_position)
|
||||
target_offset = walk_to_offset(entity)
|
||||
target_offset_direction = OFFSET_DIRECTIONS.sample
|
||||
target_facing_direction = walk_to_facing_direction(entity)
|
||||
|
||||
unless target_facing_direction.nil?
|
||||
if front
|
||||
target_offset_direction = target_facing_direction
|
||||
elsif behind
|
||||
target_offset_direction = target_facing_direction.opposite
|
||||
end
|
||||
end
|
||||
|
||||
target_offset_position = target_position.step(target_offset, target_offset_direction)
|
||||
return if target_offset_position.eql?(position)
|
||||
|
||||
pathfinder = FOLLOW_PATH_FINDER
|
||||
path = pathfinder.find(position, target_offset_position)
|
||||
path.each { |tile| self.walking_queue.add_step(tile) }
|
||||
end
|
||||
end
|
||||
|
||||
MobExtension::register(WalkToMobExtension)
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Gets the number of tiles away from an entity's actual origin that we can
|
||||
# walk to.
|
||||
def walk_to_offset(entity)
|
||||
case entity.entity_type
|
||||
when EntityType::DYNAMIC_OBJECT, EntityType::STATIC_OBJECT
|
||||
return max(entity.definition.width, entity.definition.length) + 1
|
||||
when EntityType::NPC
|
||||
return entity.definition.size + 1
|
||||
when EntityType::PLAYER
|
||||
return 1
|
||||
else
|
||||
fail "walk_to_offset called with invalid entity type: #{type.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Gets the direction that an entity is facing, so a mob can walk up infront of them.
|
||||
def walk_to_facing_direction(entity)
|
||||
case entity.entity_type
|
||||
when EntityType::DYNAMIC_OBJECT, EntityType::STATIC_OBJECT
|
||||
return Direction::WNES[entity.orientation]
|
||||
when EntityType::NPC, EntityType::PLAYER
|
||||
return entity.last_direction
|
||||
else
|
||||
fail "walk_to_offset called with invalid entity type: #{type.to_s}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,197 @@
|
||||
require 'java'
|
||||
|
||||
java_import 'org.apollo.cache.def.NpcDefinition'
|
||||
java_import 'org.apollo.game.action.Action'
|
||||
java_import 'org.apollo.game.model.Animation'
|
||||
java_import 'org.apollo.game.model.Graphic'
|
||||
java_import 'org.apollo.game.model.Position'
|
||||
java_import 'org.apollo.game.model.World'
|
||||
java_import 'org.apollo.game.model.entity.Npc'
|
||||
|
||||
# Information about npc spawning
|
||||
#
|
||||
# Npcs are passed to spawn npc as a hash. Every key and every non-integer value must be a Symbol.
|
||||
# Every hash must implement the following:
|
||||
# :name - the name of the npc. If this npc shares its name with another, append the specific id
|
||||
# after the name (e.g. :woman_4)
|
||||
# :x - the x coordinate where the npc will spawn.
|
||||
# :y - the y coordinate where the npc will spawn.
|
||||
# Optional arguments are as follows:
|
||||
# :face - the direction the npc should face when it spawns. Supported options are :north,
|
||||
# :north_east, :east, :south_east, :south, :south_west, :west, and :north_west
|
||||
# :bounds - the rectangular bound that the npc can wander about in. Order is
|
||||
# [bottom-left x-coordinate, bottom-left y-coordinate, top-right x-coordinate,
|
||||
# top-right y-coordinate]
|
||||
# :delta_bounds - the rectangular bound that the npc can wander about in, as a difference from
|
||||
# the spawn point. Order is [x-delta, y-delta]. Should not be used with :bounds.
|
||||
# :spawn_animation - the animation that will be played when the npc spawns.
|
||||
# :spawn_graphic - the graphic that will be played when the npc spawns.
|
||||
|
||||
# Spawns an npc with the properties specified in the hash.
|
||||
def spawn_npc(hash)
|
||||
unless (hash.key?(:name) || hash.key?(:id)) && hash.has_keys?(:x, :y)
|
||||
fail 'A name (or id), x coordinate, and y coordinate must be specified to spawn an npc.'
|
||||
end
|
||||
|
||||
npc = get_npc(hash)
|
||||
spawn(npc, hash)
|
||||
npc
|
||||
end
|
||||
|
||||
# Spawns the specified npc and applies the properties in the hash.
|
||||
def spawn(npc, hash)
|
||||
unless hash.empty?
|
||||
hash = decode_hash(npc.position, hash)
|
||||
apply_decoded_hash(npc, hash)
|
||||
end
|
||||
|
||||
$world.register(npc)
|
||||
end
|
||||
|
||||
# Returns an npc with the id and position specified by the hash.
|
||||
def get_npc(hash)
|
||||
id = lookup_npc(hash.delete(:name))
|
||||
|
||||
z = hash.delete(:z)
|
||||
position = Position.new(hash.delete(:x), hash.delete(:y), z.nil? ? 0 : z)
|
||||
Npc.new($world, id, position)
|
||||
end
|
||||
|
||||
# Applies a decoded hash (one aquired using parse_hash) to the specified npc.
|
||||
def apply_decoded_hash(npc, hash)
|
||||
hash.each do |key, value|
|
||||
case key
|
||||
when :face then npc.turn_to(value)
|
||||
when :boundary then npc.boundaries = value
|
||||
when :spawn_animation then npc.play_animation(Animation.new(value))
|
||||
when :spawn_graphic then npc.play_graphic(Graphic.new(value))
|
||||
else fail "Unrecognised key #{key} - value #{value}."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Parses the remaining key-value pairs in the hash.
|
||||
def decode_hash(position, hash)
|
||||
decoded = {}
|
||||
|
||||
hash.each do |key, value|
|
||||
case key
|
||||
when :face
|
||||
decoded[:face] = direction_to_position(value, position)
|
||||
when :delta_bounds
|
||||
fail ':delta_bounds must have two values.' unless value.length == 2
|
||||
dx, dy, x, y, z = value[0], value[1], position.x, position.y, position.height
|
||||
fail 'Delta values cannot be less than 0.' if dx < 0 || dy < 0
|
||||
|
||||
decoded[:boundary] = [Position.new(x - dx, y - dy, z), Position.new(x + dx, y + dy, z)]
|
||||
when :bounds
|
||||
fail ':bounds must have four values.' unless value.length == 4
|
||||
min_x, min_y, max_x, max_y = value[0], value[1], value[2], value[3]
|
||||
|
||||
decoded[:boundary] = [Position.new(min_x, min_y), Position.new(max_x, max_y)]
|
||||
when :spawn_animation then decoded[:spawn_animation] = Animation.new(value)
|
||||
when :spawn_graphic then decoded[:spawn_graphic] = Graphic.new(value)
|
||||
else fail "Unrecognised key #{key} - value #{value}."
|
||||
end
|
||||
end
|
||||
|
||||
decoded
|
||||
end
|
||||
|
||||
# Returns a position that an entity at the specified position should be facing towards if they are
|
||||
# looking in the specified direction.
|
||||
def direction_to_position(direction, position)
|
||||
x, y, z = position.x, position.y, position.height
|
||||
|
||||
case direction
|
||||
when :north then return Position.new(x, y + 1, z)
|
||||
when :north_east then return Position.new(x + 1, y + 1, z)
|
||||
when :east then return Position.new(x + 1, y, z)
|
||||
when :south_east then return Position.new(x + 1, y - 1, z)
|
||||
when :south then return Position.new(x, y - 1, z)
|
||||
when :south_west then return Position.new(x - 1, y - 1, z)
|
||||
when :west then return Position.new(x - 1, y, z)
|
||||
when :north_west then return Position.new(x - 1, y + 1, z)
|
||||
else return position
|
||||
end
|
||||
end
|
||||
|
||||
# An action that spawns an npc temporarily, before executing an action.
|
||||
class TemporaryNpcAction < Action
|
||||
attr_reader :executions, :combative
|
||||
|
||||
def initialize(delay, immediate, hash)
|
||||
super(delay, immediate, get_npc(hash))
|
||||
|
||||
@executions = 0
|
||||
@hash = hash
|
||||
end
|
||||
|
||||
def execute
|
||||
if @executions == 0
|
||||
spawn(mob, @hash)
|
||||
execute_spawn_action
|
||||
else
|
||||
execute_action
|
||||
end
|
||||
|
||||
@executions += 1
|
||||
end
|
||||
|
||||
def execute_action
|
||||
# Override to provide functionality.
|
||||
end
|
||||
|
||||
def execute_spawn_action
|
||||
# Overridden to provide functionality for when the npc spawns.
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# A random event that spawns and executes some sort of action.
|
||||
class RandomEvent < TemporaryNpcAction
|
||||
|
||||
def initialize(delay, immediate, hash, combative, target)
|
||||
super(delay, immediate, hash)
|
||||
|
||||
@combative = combative
|
||||
@target = target
|
||||
end
|
||||
|
||||
def execute_spawn_action
|
||||
mob.turn_to(target.position)
|
||||
mob.update_interacting_mob(target.index)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Adds a random event to the array.
|
||||
def register_random_event(event)
|
||||
RANDOM_EVENTS << event
|
||||
end
|
||||
|
||||
# Contains random event npcs
|
||||
RANDOM_EVENTS = []
|
||||
|
||||
# Spawns a random event for the specified player.
|
||||
def send_random_event(player)
|
||||
position = player.position
|
||||
npc_position = Position.new(position.x + 1, position.y, position.height)
|
||||
# TODO: Find an unoccupied tile instead of the assumption that (x + 1) is traversable!!
|
||||
|
||||
spawn_random_event(npc_position, false)
|
||||
end
|
||||
|
||||
# Spawns a random event in the specified position.
|
||||
# If 'combat' is false, only non-combat events will be spawned.
|
||||
def spawn_random_event(_position, _combat)
|
||||
event = RANDOM_EVENTS[rand(RANDOM_EVENTS.size)]
|
||||
attempts = 0
|
||||
|
||||
while event.combative && attempts < 5
|
||||
event = RANDOM_EVENTS[rand(RANDOM_EVENTS.size)]
|
||||
attempts += 1
|
||||
end
|
||||
|
||||
event.execute unless attempts == 5 # 5 iterations is plenty, just give up at this point
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<plugin>
|
||||
<id>entity-spawning</id>
|
||||
<version>0.1</version>
|
||||
<name>Entity Spawning</name>
|
||||
<description>Adds support for easy ruby entity spawning.</description>
|
||||
<authors>
|
||||
<author>Major</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>npc-spawn.rb</script>
|
||||
</scripts>
|
||||
<dependencies>
|
||||
<dependency>util</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
Reference in New Issue
Block a user