diff --git a/assets/base/alternate_tank/scripts/vehicle.rb b/assets/base/alternate_tank/scripts/vehicle.rb index cbe4fb2..3f331fe 100644 --- a/assets/base/alternate_tank/scripts/vehicle.rb +++ b/assets/base/alternate_tank/scripts/vehicle.rb @@ -1,9 +1,10 @@ -context Vehicle # Generic, Weapon +component(:vehicle) # Generic, Weapon on.button_down(:interact) do |event| - if event.player.touching?(event.entity) - event.player.enter_vehicle - elsif event.player.driving?(event.entity) or event.player.passenger?(event.entity) - event.player.exit_vehicle - end + $window.console.stdin("#{event.entity.name} handled button_down(:interact)") + # if event.player.touching?(event.entity) + # event.player.enter_vehicle + # elsif event.player.driving?(event.entity) or event.player.passenger?(event.entity) + # event.player.exit_vehicle + # end end \ No newline at end of file diff --git a/assets/base/power_plant/scripts/power_plant.rb b/assets/base/power_plant/scripts/power_plant.rb index 4459ddd..25ef7c1 100644 --- a/assets/base/power_plant/scripts/power_plant.rb +++ b/assets/base/power_plant/scripts/power_plant.rb @@ -1,8 +1,8 @@ -context Building +component(:building) on.create do |event| - map.add_entity("base", "purchase_terminal", Vector.new(0, 1.5, 0), Vector.new(0, 90, 0), data: {team: event.entity.team}) - map.add_entity("base", "information_panel", Vector.new(2, 1.5, 0), Vector.new(0, 0, 0)) - map.add_entity("base", "door", Vector.new(2, 0, 6), Vector.new(0, 0, 0)) - map.add_entity("base", "door", Vector.new(2, 0, 6), Vector.new(0, 180, 0)) + event.map.insert_entity("base", "purchase_terminal", event.entity.position + Vector.new(1.5, 1.5, 4.52), Vector.new(0, 200, 0), data: {team: nil}) + event.map.insert_entity("base", "information_panel", event.entity.position + Vector.new(-3, 0, -1), Vector.new(0, 90, 0)) + event.map.insert_entity("base", "door", event.entity.position + Vector.new(0, 0, -6), Vector.new(0, 0, 0)) + event.map.insert_entity("base", "door", event.entity.position + Vector.new(0, 0, -6), Vector.new(0, 180, 0)) end \ No newline at end of file diff --git a/assets/base/war_factory/manifest.yaml b/assets/base/war_factory/manifest.yaml index 3f4909d..ffd3907 100644 --- a/assets/base/war_factory/manifest.yaml +++ b/assets/base/war_factory/manifest.yaml @@ -1,2 +1,5 @@ name: "war_factory" -model: "war_factory.obj" \ No newline at end of file +model: "war_factory.obj" +scripts: [ + "war_factory" +] \ No newline at end of file diff --git a/assets/base/war_factory/scripts/war_factory.rb b/assets/base/war_factory/scripts/war_factory.rb new file mode 100644 index 0000000..cf58934 --- /dev/null +++ b/assets/base/war_factory/scripts/war_factory.rb @@ -0,0 +1,8 @@ +component(:building) + +on.create do |event| + event.map.insert_entity("base", "purchase_terminal", event.entity.position + Vector.new(6, 1.5, 3), Vector.new(0, -90, 0), data: {team: nil}) + event.map.insert_entity("base", "information_panel", event.entity.position + Vector.new(0.5, 0, 3), Vector.new(0, 90, 0)) + event.map.insert_entity("base", "door", event.entity.position + Vector.new(3.3, 0, 6), Vector.new(0, 0, 0)) + event.map.insert_entity("base", "door", event.entity.position + Vector.new(3.3, 0, 6), Vector.new(0, 180, 0)) +end \ No newline at end of file diff --git a/blends/power_plant.blend b/blends/power_plant.blend index b059369..6dd8b5a 100644 Binary files a/blends/power_plant.blend and b/blends/power_plant.blend differ diff --git a/blends/power_plant.blend1 b/blends/power_plant.blend1 index 90a7d06..b059369 100644 Binary files a/blends/power_plant.blend1 and b/blends/power_plant.blend1 differ diff --git a/i-mic-fps.rb b/i-mic-fps.rb index 560b03d..4f5f1eb 100644 --- a/i-mic-fps.rb +++ b/i-mic-fps.rb @@ -56,6 +56,17 @@ require_relative "lib/ui/menus/main_menu" require_relative "lib/states/game_states/game" require_relative "lib/states/game_states/loading_state" +require_relative "lib/scripting" +require_relative "lib/subscription" +require_relative "lib/publisher" +require_relative "lib/event" +require_relative "lib/event_handler" +require_relative "lib/event_handlers/input" +require_relative "lib/event_handlers/entity_lifecycle" + +require_relative "lib/component" +require_relative "lib/components/building" + require_relative "lib/game_objects/entity" require_relative "lib/game_objects/model_loader" require_relative "lib/game_objects/light" diff --git a/lib/component.rb b/lib/component.rb new file mode 100644 index 0000000..f83d125 --- /dev/null +++ b/lib/component.rb @@ -0,0 +1,40 @@ +class IMICFPS + class Component + COMPONENTS = {} + + def self.get(name) + COMPONENTS.dig(name) + end + + def self.inherited(subclass) + pp subclass + COMPONENTS["__pending"] ||= [] + COMPONENTS["__pending"] << subclass + end + + def self.initiate + COMPONENTS["__pending"].each do |klass| + component = klass.new + COMPONENTS[component.name] = component + end + + COMPONENTS.delete("__pending") + end + + def initialize + setup + end + + def name + string = self.class.name.split("::").last + split = string.scan(/[A-Z][a-z]*/) + + component_name = "#{split.map { |s| s.downcase }.join("_")}".to_sym + + return component_name + end + + def setup + end + end +end \ No newline at end of file diff --git a/lib/components/building.rb b/lib/components/building.rb new file mode 100644 index 0000000..5b62a61 --- /dev/null +++ b/lib/components/building.rb @@ -0,0 +1,6 @@ +class IMICFPS + class Components + class Building < Component + end + end +end \ No newline at end of file diff --git a/lib/event.rb b/lib/event.rb new file mode 100644 index 0000000..5cf2eed --- /dev/null +++ b/lib/event.rb @@ -0,0 +1,10 @@ +class IMICFPS + class EventHandler + class Event + attr_reader :entity, :context, :map, :player + def initialize(entity:, context: nil, map: $window.current_state, player: nil) + @entity, @context, @map, @player = entity, context, map, player + end + end + end +end \ No newline at end of file diff --git a/lib/event_handler.rb b/lib/event_handler.rb new file mode 100644 index 0000000..59ff36f --- /dev/null +++ b/lib/event_handler.rb @@ -0,0 +1,37 @@ +class IMICFPS + class EventHandler + @@handlers = {} + + def self.inherited(subclass) + @@handlers["__pending"] ||= [] + + @@handlers["__pending"] << subclass + end + + def self.initiate + @@handlers["__pending"].each do |handler| + instance = handler.new + instance.handles.each do |event| + @@handlers[event] = instance + end + end + + @@handlers.delete("__pending") + end + + def self.get(event) + @@handlers.dig(event) + end + + def initialize + end + + def handlers + raise NotImplementedError + end + + def handle(subscriber, context, *args) + raise NotImplementedError + end + end +end \ No newline at end of file diff --git a/lib/event_handlers/entity_lifecycle.rb b/lib/event_handlers/entity_lifecycle.rb new file mode 100644 index 0000000..b16b348 --- /dev/null +++ b/lib/event_handlers/entity_lifecycle.rb @@ -0,0 +1,16 @@ +class IMICFPS + class EventHandler + class EntityLifeCycle < EventHandler + def handles + [:create, :entity_move, :destroy] + end + + def handle(subscriber, context, *args) + return unless subscriber.entity == args.first.first + event = EventHandler::Event.new(entity: subscriber.entity, context: context) + + subscriber.trigger(event) + end + end + end +end \ No newline at end of file diff --git a/lib/event_handlers/input.rb b/lib/event_handlers/input.rb new file mode 100644 index 0000000..68735ec --- /dev/null +++ b/lib/event_handlers/input.rb @@ -0,0 +1,24 @@ +class IMICFPS + class EventHandler + class Input < EventHandler + def handles + [:button_down, :button_up] + end + + def handle(subscriber, context, *args) + action = subscriber.args.flatten.first + key = args.flatten.first + + event = EventHandler::Event.new(entity: subscriber.entity, context: context, player: context) + + if action.is_a?(Numeric) && action == key + subscriber.trigger(event) + else + if InputMapper.get(action) == key + subscriber.trigger(event) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/game_objects/entity.rb b/lib/game_objects/entity.rb index 112266f..479aff9 100644 --- a/lib/game_objects/entity.rb +++ b/lib/game_objects/entity.rb @@ -4,6 +4,7 @@ class IMICFPS # A game object is any renderable thing class Entity include CommonMethods + include Scripting attr_accessor :scale, :visible, :renderable, :backface_culling attr_accessor :position, :orientation, :velocity @@ -38,6 +39,8 @@ class IMICFPS @delta_time = Gosu.milliseconds @last_position = Vector.new(@position.x, @position.y, @position.z) + load_scripts + setup if @bound_model @@ -52,6 +55,20 @@ class IMICFPS return self end + def load_scripts + @manifest.scripts.each do |script| + instance_eval(script.source) + end + end + + def method_missing(method, *args, &block) + unless component = Component.get(method) + raise NoMemoryError, "undefined method '#{method}' for #<#{self.class}:#{self.object_id}>" + else + return component + end + end + def collidable? @collidable.include?(@collision) end diff --git a/lib/managers/collision_manager.rb b/lib/managers/collision_manager.rb index f0662f6..a14effd 100644 --- a/lib/managers/collision_manager.rb +++ b/lib/managers/collision_manager.rb @@ -91,10 +91,10 @@ class IMICFPS vs = a vs = b if a == entity - broadphase = search(Ray.new(entity.position, Vector.new(0, -1, 0), entity.velocity.y.abs)) + broadphase = search(Ray.new(entity.position, Vector.down, entity.velocity.y.abs)) broadphase.detect do |ent| - ray = Ray.new(entity.position - ent.position, Vector.new(0, -1, 0)) + ray = Ray.new(entity.position - ent.position, Vector.down) if ent.model.aabb_tree.search(ray).size > 0 on_ground = true return true diff --git a/lib/managers/entity_manager.rb b/lib/managers/entity_manager.rb index f9bc80a..2c61b88 100644 --- a/lib/managers/entity_manager.rb +++ b/lib/managers/entity_manager.rb @@ -2,9 +2,15 @@ class IMICFPS module EntityManager # Get included into GameState context def add_entity(entity) @collision_manager.add(entity)# Add every entity to collision manager + @publisher.publish(:create, self, entity) @entities << entity end + def insert_entity(package, name, position, orientation, data = {}) + ent = Map::Entity.new(package, name, position, orientation, Vector.new(1,1,1)) + add_entity(IMICFPS::Entity.new(map_entity: ent, manifest: Manifest.new(package: package, name: name))) + end + def find_entity(entity) @entities.detect {|entity| entity == entity} end @@ -13,6 +19,7 @@ class IMICFPS ent = @entities.detect {|entity| entity == entity} if ent @collision_manager.remove(entity) + @publisher.publish(:destroy, self, entity) @entities.delete(ent) end end diff --git a/lib/managers/input_mapper.rb b/lib/managers/input_mapper.rb index b11ac0c..03c5e9c 100644 --- a/lib/managers/input_mapper.rb +++ b/lib/managers/input_mapper.rb @@ -115,6 +115,8 @@ IMICFPS::InputMapper.set(:jump, Gosu::KbSpace) IMICFPS::InputMapper.set(:sprint, [Gosu::KbLeftControl]) IMICFPS::InputMapper.set(:turn_180, Gosu::KbX) +IMICFPS::InputMapper.set(:interact, Gosu::KbE) + IMICFPS::InputMapper.set(:ascend, Gosu::KbSpace) IMICFPS::InputMapper.set(:descend, Gosu::KbC) IMICFPS::InputMapper.set(:toggle_first_person_view, Gosu::KbF) diff --git a/lib/manifest.rb b/lib/manifest.rb index 6ada404..a78780e 100644 --- a/lib/manifest.rb +++ b/lib/manifest.rb @@ -12,8 +12,6 @@ class IMICFPS @file = manifest_file parse(manifest_file) - - pp @scripts end def parse(file) diff --git a/lib/publisher.rb b/lib/publisher.rb new file mode 100644 index 0000000..5032c82 --- /dev/null +++ b/lib/publisher.rb @@ -0,0 +1,39 @@ +class IMICFPS + class Publisher + def self.subscribe(subscription) + raise "Expected IMICFPS::Subscription not #{subscription.class}" unless subscription.is_a?(IMICFPS::Subscription) + Publisher.instance.add_sub(subscription) + end + + def self.unsubscribe(subscription) + end + + def self.instance + @@instance + end + + def initialize + @@instance = self + EventHandler.initiate + Component.initiate + @events = {} + end + + def add_sub(subscription) + raise "Expected IMICFPS::Subscription not #{subscription.class}" unless subscription.is_a?(IMICFPS::Subscription) + @events[subscription.event] ||= [] + + @events[subscription.event] << subscription + end + + def publish(event, context, *args) + if subscribers = @events.dig(event) + return unless event_handler = EventHandler.get(event) + + subscribers.each do |subscriber| + event_handler.handle(subscriber, context, args) + end + end + end + end +end \ No newline at end of file diff --git a/lib/scripting.rb b/lib/scripting.rb new file mode 100644 index 0000000..9a49e40 --- /dev/null +++ b/lib/scripting.rb @@ -0,0 +1,11 @@ +class IMICFPS + module Scripting + def on + Subscription.new(self) + end + + def component(name) + Component.get(name) + end + end +end \ No newline at end of file diff --git a/lib/states/game_states/game.rb b/lib/states/game_states/game.rb index f4b9c10..93ca3f4 100644 --- a/lib/states/game_states/game.rb +++ b/lib/states/game_states/game.rb @@ -5,6 +5,8 @@ class IMICFPS def setup @collision_manager = CollisionManager.new(game_state: self) @renderer = Renderer.new(game_state: self) + @publisher = Publisher.new + @map = @options[:map] add_entity(Terrain.new(map_entity: @map.terrain, manifest: Manifest.new(package: @map.terrain.package, name: @map.terrain.name))) @@ -85,6 +87,8 @@ class IMICFPS @last_frame_time = Gosu.milliseconds update_text + @publisher.publish(:tick, Gosu.milliseconds - @delta_time) + @collision_manager.update @entities.each(&:update) @@ -202,6 +206,7 @@ eos @demo_changed = true end InputMapper.keydown(id) + @publisher.publish(:button_down, nil, id) @entities.each do |entity| entity.button_down(id) if defined?(entity.button_down) @@ -218,6 +223,7 @@ eos @demo_changed = true end InputMapper.keyup(id) + @publisher.publish(:button_up, nil, id) @entities.each do |entity| entity.button_up(id) if defined?(entity.button_up) diff --git a/lib/subscription.rb b/lib/subscription.rb new file mode 100644 index 0000000..3876807 --- /dev/null +++ b/lib/subscription.rb @@ -0,0 +1,40 @@ +class IMICFPS + class Subscription + attr_reader :entity, :event, :args, :block + def initialize(entity) + @entity = entity + + @event = nil + @args = nil + @block = nil + end + + def method_missing(event, *args, &block) + return unless subscribable_events.include?(event) + + @event, @args, @block = event, args, block + Publisher.subscribe(self) + end + + def trigger(event, *args) + if @block + @block.call(event, *args) + end + end + + private def subscribable_events + [ + :tick, + :create, + :destroy, + :button_down, :button_up, + :mouse_move, + :entity_move, + :interact, + :player_join, :player_leave, :player_die, + :pickup_item, :use_item, :drop_item, + :enter_vehicle, :exit_vehicle, + ] + end + end +end \ No newline at end of file diff --git a/lib/window.rb b/lib/window.rb index bac554c..d48d941 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -3,6 +3,7 @@ class IMICFPS attr_accessor :number_of_vertices, :needs_cursor attr_reader :camera + attr_reader :console def initialize(window_width = 1280, window_height = 800, fullscreen = false) fps_target = (ARGV.first.to_i != 0) ? ARGV.first.to_i : 60 if ARGV.join.include?("--native") diff --git a/maps/test_map.json b/maps/test_map.json index 2d8d532..c2ecb03 100644 --- a/maps/test_map.json +++ b/maps/test_map.json @@ -85,70 +85,6 @@ "scale": 1, "scripts": [] }, - { - "package":"base", - "name":"purchase_terminal", - "position": { - "x":37.8788, - "y":1.843869, - "z":-6.06061 - }, - "orientation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": 1, - "scripts": [] - }, - { - "package":"base", - "name":"information_panel", - "position": { - "x":37.8788, - "y":0.343869, - "z":-6.06061 - }, - "orientation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": 1, - "scripts": [] - }, - { - "package":"base", - "name":"door", - "position": { - "x":37.8788, - "y":0.343869, - "z":-12.06061 - }, - "orientation": { - "x": 0, - "y": 0, - "z": 0 - }, - "scale": 1, - "scripts": [] - }, - { - "package":"base", - "name":"door", - "position": { - "x":37.8788, - "y":0.343869, - "z":-12.06061 - }, - "orientation": { - "x": 0, - "y": 180, - "z": 0 - }, - "scale": 1, - "scripts": [] - }, { "package":"base", "name":"war_factory",