2 Commits

Author SHA1 Message Date
9b215d4b1a Removed border from links 2021-03-27 20:13:41 -05:00
86e5f879dd WIP changes sync 2021-03-22 22:11:11 -05:00
68 changed files with 895 additions and 1386 deletions

View File

@@ -1,8 +1,8 @@
GIT
remote: https://github.com/cyberarm/cyberarm_engine
revision: 1b080f9fb99cc4ec696f32f00f28cbf89b40ed04
revision: 20970e5aa9fbcad6d4bf7d9b56cbd06ba5c53275
specs:
cyberarm_engine (0.18.0)
cyberarm_engine (0.17.1)
clipboard (~> 1.3.5)
excon (~> 0.78.0)
gosu (~> 1.1)
@@ -25,19 +25,19 @@ GEM
concurrent-ruby (1.1.8)
cri (2.1.0)
excon (0.78.1)
gosu (1.2.0)
gosu (1.1.0)
gosu_more_drawables (0.3.1)
i18n (1.8.10)
i18n (1.8.8)
concurrent-ruby (~> 1.0)
mini_portile2 (2.8.0)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
mini_portile2 (2.5.0)
nokogiri (1.11.1)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
nokogiri (1.13.6-x64-mingw32)
nokogiri (1.11.1-x64-mingw32)
racc (~> 1.4)
ocra (1.3.11)
opengl-bindings (1.6.11)
racc (1.6.0)
opengl-bindings (1.6.10)
racc (1.5.2)
rake (13.0.3)
rubyzip (2.3.0)

View File

@@ -10,7 +10,7 @@ Creating a multiplayer first-person-shooter in pure Ruby; Using C extensions onl
![screenshot](https://raw.githubusercontent.com/cyberarm/i-mic-fps/master/screenshots/screenshot-game.png)
## Using
Ruby 3.0+ interpeter with support for the Gosu game library C extension.
Ruby 2.5+ interpeter with support for the Gosu game library C extension.
* Clone or download this repo
* `bundle install`
* `bundle exec ruby i-mic-fps.rb [options]`

View File

@@ -1,22 +0,0 @@
{
"playlists": {
"menus": [
"menu_background"
],
"nighttime": [],
"daytime": []
},
"music": [
{
"name": "menu_background",
"path": "music/untitled-2-revised-extended_mixed.ogg"
}
],
"sounds": [
{
"name": "shield_regen",
"type": "sfx",
"path": "sfx/shield_regen.wav"
}
]
}

View File

@@ -0,0 +1,11 @@
---
playlists:
- menus:
- nighttime:
- daytime:
music:
sounds:
-
name: shield_regen
type: sfx
path: sfx/shield_regen.wav

View File

@@ -31,7 +31,6 @@ include GLU
def require_all(directory)
files = Dir["#{directory}/**/*.rb"].sort!
file_order = []
loop do
failed = []
@@ -40,7 +39,6 @@ def require_all(directory)
files.each do |file|
begin
require_relative file
file_order << file
rescue NameError => e
failed << file
first_name_error ||= e
@@ -54,8 +52,6 @@ def require_all(directory)
end
break if failed.empty?
end
# pp file_order.map { |f| f.gsub(".rb", "")}
end
require_all "lib"
@@ -79,36 +75,17 @@ end
if prevent_launch?[0]
puts prevent_launch?[1]
else
native = ARGV.join.include?("--native")
fps_target = ARGV.first.to_i != 0 ? ARGV.first.to_i : 60
window_width = native ? Gosu.screen_width : 1280
window_height = native ? Gosu.screen_height : 720
window_fullscreen = native ? true : false
window = IMICFPS::Window.new(
width: window_width,
height: window_height,
fullscreen: window_fullscreen,
resizable: !window_fullscreen,
update_interval: 1000.0 / fps_target
)
if ARGV.join.include?("--profile")
begin
require "ruby-prof"
RubyProf.start
window.show
result = RubyProf.stop
printer = RubyProf::MultiPrinter.new(result)
printer.print(path: ".", profile: "profile", min_percent: 2)
rescue LoadError
puts "ruby-prof not installed!"
raise
end
else
window.show
elsif ARGV.join.include?("--profile")
begin
require "ruby-prof"
RubyProf.start
IMICFPS::Window.new.show
result = RubyProf.stop
printer = RubyProf::MultiPrinter.new(result)
printer.print(path: ".", profile: "profile", min_percent: 2)
rescue LoadError
puts "ruby-prof not installed!"
end
else
IMICFPS::Window.new.show
end

View File

@@ -7,7 +7,7 @@ class IMICFPS
attr_accessor :mode, :camera, :entity, :distance, :origin_distance,
:constant_pitch, :mouse_sensitivity, :mouse_captured
def initialize(camera:, entity: nil, mode: :fpv)
def initialize(camera:, entity:, mode: :fpv)
# :fpv - First Person View
# :tpv - Third Person View
@mode = mode
@@ -63,7 +63,7 @@ class IMICFPS
end
def update
position_camera if @entity
position_camera
return unless @mouse_captured
@@ -74,10 +74,8 @@ class IMICFPS
@camera.orientation.x -= Float(@true_mouse.y - window.mouse_y) / (@mouse_sensitivity * @camera.field_of_view) * 70
@camera.orientation.x = @camera.orientation.x.clamp(-90.0, 90.0)
if @entity
@entity.orientation.y += delta
@entity.orientation.y %= 360.0
end
@entity.orientation.y += delta
@entity.orientation.y %= 360.0
window.mouse_x = window.width / 2 if window.mouse_x <= 1 || window.mouse_x >= window.width - 1
window.mouse_y = window.height / 2 if window.mouse_y <= 1 || window.mouse_y >= window.height - 1
@@ -101,49 +99,14 @@ class IMICFPS
@camera.max_view_distance += 0.5
elsif actions.include?(:toggle_first_person_view)
@mode = first_person_view? ? :tpv : :fpv
@entity.visible = !first_person_view? if @entity
@entity.visible = !first_person_view?
elsif actions.include?(:turn_180)
@entity.orientation.y += 180 if @entity
@entity.orientation.y %= 360.0 if @entity
@entity.orientation.y += 180
@entity.orientation.y %= 360.0
end
end
def button_up(id)
end
def free_move
relative_y_rotation = (@camera.orientation.y + 180)
relative_speed = 2.5
relative_speed = 1.5 if InputMapper.down?(:sneak)
relative_speed = 10.0 if InputMapper.down?(:sprint)
relative_speed *= window.dt
if InputMapper.down?( :forward)
@camera.position.z += Math.cos(relative_y_rotation * Math::PI / 180) * relative_speed
@camera.position.x -= Math.sin(relative_y_rotation * Math::PI / 180) * relative_speed
end
if InputMapper.down?(:backward)
@camera.position.z -= Math.cos(relative_y_rotation * Math::PI / 180) * relative_speed
@camera.position.x += Math.sin(relative_y_rotation * Math::PI / 180) * relative_speed
end
if InputMapper.down?(:strife_left)
@camera.position.z += Math.sin(relative_y_rotation * Math::PI / 180) * relative_speed
@camera.position.x += Math.cos(relative_y_rotation * Math::PI / 180) * relative_speed
end
if InputMapper.down?(:strife_right)
@camera.position.z -= Math.sin(relative_y_rotation * Math::PI / 180) * relative_speed
@camera.position.x -= Math.cos(relative_y_rotation * Math::PI / 180) * relative_speed
end
if InputMapper.down?(:ascend)
@camera.position.y += relative_speed
end
if InputMapper.down?(:descend)
@camera.position.y -= relative_speed
end
end
end
end

View File

@@ -11,25 +11,8 @@ class IMICFPS
end
def draw
draw_rect(
window.width / 2 - @size,
(window.height / 2 - @size) - @thickness / 2,
@size * 2,
@thickness,
@color,
0,
:default
)
draw_rect(
(window.width / 2) - @thickness / 2,
window.height / 2 - (@size * 2),
@thickness,
@size * 2,
@color,
0,
:default
)
draw_rect(window.width / 2 - @size, (window.height / 2 - @size) - @thickness / 2, @size * 2, @thickness, @color, 0, :default)
draw_rect((window.width / 2) - @thickness / 2, window.height / 2 - (@size * 2), @thickness, @size * 2, @color, 0, :default)
end
end
end

View File

@@ -1,13 +0,0 @@
module CyberarmEngine
class Element
alias enter_original enter
def enter(_sender)
if @block && is_a?(CyberarmEngine::Element::Link)
get_sample("#{IMICFPS::GAME_ROOT_PATH}/static/sounds/ui_hover.ogg").play
end
enter_original(_sender)
end
end
end

View File

@@ -32,13 +32,5 @@ class IMICFPS
def update
@hud_elements.each(&:update)
end
def button_down(id)
@hud_elements.each { |e| e.button_down(id) }
end
def button_up(id)
@hud_elements.each { |e| e.button_up(id) }
end
end
end

View File

@@ -7,37 +7,21 @@ class IMICFPS
# Widget margin from screen edge
# or how much widget is pushed in
def self.vertical_margin
@@vertical_margin ||= 36
def self.margin
@@margin ||= 10
end
def self.vertical_margin=(n)
@@vertical_margin = n
end
def self.horizontal_margin
@@horizontal_margin ||= 10
end
def self.horizontal_margin=(n)
@@horizontal_margin = n
def self.padding=(n)
@@padding = n
end
# Widget element padding
def self.vertical_padding
@@vertical_padding ||= 10
def self.padding
@@margin ||= 10
end
def self.vertical_padding=(n)
@@vertical_padding = n
end
def self.horizontal_padding
@@horizontal_padding ||= 10
end
def self.horizontal_padding=(n)
@@horizontal_padding = n
def self.padding=(n)
@@padding = n
end
attr_reader :options
@@ -57,20 +41,6 @@ class IMICFPS
def update
end
def button_down(id)
end
def button_up(id)
end
def hijack_input!
$window.input_hijack = self
end
def release_input!
$window.input_hijack = nil
end
end
end
end

View File

@@ -4,14 +4,14 @@ class IMICFPS
class HUD
class AmmoWidget < HUD::Widget
def setup
@text = Text.new("", size: 64, font: MONOSPACE_FONT, border: true, border_color: Gosu::Color::BLACK)
@text = Text.new("", size: 64, mode: :add, font: MONOSPACE_FONT)
@background = Gosu::Color.new(0x88c64600)
end
def draw
Gosu.draw_rect(
@text.x - Widget.horizontal_padding, @text.y - Widget.vertical_padding,
@text.width + Widget.horizontal_padding * 2, @text.height + Widget.vertical_padding * 2,
@text.x - Widget.padding, @text.y - Widget.padding,
@text.width + Widget.padding * 2, @text.height + Widget.padding * 2,
@background
)
@text.draw
@@ -23,8 +23,8 @@ class IMICFPS
@text.text = "#{random}/999"
end
@text.x = window.width - (Widget.horizontal_margin + @text.width + Widget.horizontal_padding)
@text.y = window.height - (Widget.vertical_margin + @text.height + Widget.vertical_padding)
@text.x = window.width - (Widget.margin + @text.width + Widget.padding)
@text.y = window.height - (Widget.margin + @text.height + Widget.padding)
end
end
end

View File

@@ -4,93 +4,41 @@ class IMICFPS
class HUD
class ChatWidget < HUD::Widget
def setup
@deliver_to_text = Text.new("", size: 28, font: BOLD_SANS_FONT)
@text = Text.new("", size: 28, font: SANS_FONT)
@text_input = nil
@text = Text.new("", size: 28, mode: :add, font: SANS_FONT)
@background = Gosu::Color.new(0x88c64600)
@selection_color = Gosu::Color.new(0x88222222)
@width = @options[:width] || 400
@delivery_options = [:all, :team, :squad]
end
def draw
return unless @text_input
return unless window.text_input
Gosu.draw_rect(
Widget.horizontal_margin, $window.height / 2 - (@text.height / 2 + Widget.horizontal_padding),
@width - Widget.horizontal_padding * 2, @text.height + Widget.vertical_padding * 2,
@text.x - Widget.padding, @text.y - Widget.padding,
@text.width + Widget.padding * 2, @text.height + Widget.padding * 2,
@background
)
@deliver_to_text.draw
clip_width = @deliver_to_text.width + Widget.horizontal_padding * 3 + Widget.horizontal_margin
Gosu.clip_to(@text.x, @text.y, @width - clip_width, @text.height) do
x = Widget.horizontal_margin + Widget.horizontal_padding + @deliver_to_text.width
cursor_x = x + @text.width(@text_input.text[0...@text_input.caret_pos])
selection_x = x + @text.width(@text_input.text[0...@text_input.selection_start])
selection_width = cursor_x - selection_x
cursor_thickness = 2
Gosu.draw_rect(selection_x, @text.y, selection_width, @text.height, @selection_color)
Gosu.draw_rect(cursor_x, @text.y, cursor_thickness, @text.height, Gosu::Color::WHITE)
@text.draw
end
@text.draw
end
def update
@deliver_to_text.text = "#{@deliver_to}: "
@deliver_to_text.x = Widget.horizontal_margin + Widget.horizontal_padding
@deliver_to_text.y = $window.height / 2 - (@text.height / 2)
@text.text = @text_input&.text.to_s
@text.x = Widget.horizontal_margin + Widget.horizontal_padding + @deliver_to_text.width
@text.y = $window.height / 2 - (@text.height / 2)
end
def button_down(id)
# TODO: Use InputMapper keymap to function
# NOTE: Account for Y in QWERTZ layout
case id
when Gosu::KB_T, Gosu::KB_Y, Gosu::KB_U
return if @text_input
text = window.text_input&.text
hijack_input!
@text_input = window.text_input = Gosu::TextInput.new
if window.text_input.nil? && (Gosu.button_down?(Gosu::KbT) || Gosu.button_down?(Gosu::KbY) || Gosu.button_down?(Gosu::KbU))
window.text_input = Gosu::TextInput.new
@deliver_to = :all if Gosu.button_down?(Gosu::KbT)
@deliver_to = :team if Gosu.button_down?(Gosu::KbY)
@deliver_to = :squad if Gosu.button_down?(Gosu::KbU)
when Gosu::KB_TAB
return unless @text_input
cycle_deliver_to
end
end
def button_up(id)
return unless @text_input
case id
when Gosu::KB_ENTER, Gosu::KB_RETURN
release_input!
# TODO: Deliver message to server
@text_input = window.text_input = nil
when Gosu::KB_ESCAPE
release_input!
@text_input = window.text_input = nil
if window.text_input && (Gosu.button_down?(Gosu::KbEnter) || Gosu.button_down?(Gosu::KbReturn))
window.text_input = nil
end
end
def cycle_deliver_to
i = @delivery_options.index(@deliver_to)
@deliver_to = @delivery_options[(i + 1) % (@delivery_options.size)]
@text.text = text.to_s
@text.x = window.width / 2 - (Widget.margin + @text.width / 2 + Widget.padding)
@text.y = window.height - (Widget.margin + @text.height + Widget.padding)
end
end
end

View File

@@ -9,9 +9,12 @@ class IMICFPS
@text = CyberarmEngine::Text.new(
"",
size: 16,
x: Widget.horizontal_margin, y: Widget.vertical_margin, z: 45,
border_color: Gosu::Color::BLACK,
font: BOLD_SANS_FONT
x: Widget.margin, y: Widget.margin, z: 45,
shadow_size: 0.5,
shadow_alpha: 0,
shadow_color: Gosu::Color::WHITE,
mode: :add,
font: SANS_FONT
)
@last_message_time = 0

View File

@@ -5,7 +5,7 @@ class IMICFPS
class HealthWidget < HUD::Widget
def setup
@spacer = 0
@text = Text.new("", font: MONOSPACE_FONT, border: true, border_color: Gosu::Color::BLACK)
@text = Text.new("", mode: :add, font: MONOSPACE_FONT)
@width = 512
@height = 24
@slant = 32
@@ -19,10 +19,10 @@ class IMICFPS
def draw
@text.draw
fill_quad(
window.width / 2 - @width / 2, @spacer + Widget.vertical_margin, # TOP LEFT
window.width / 2 + @width / 2, @spacer + Widget.vertical_margin, # TOP RIGHT
window.width / 2 + @width / 2 - @slant, @spacer + Widget.vertical_margin + @height, # BOTTOM RIGHT
window.width / 2 - @width / 2 + @slant, @spacer + Widget.vertical_margin + @height, # BOTTOM LEFT
window.width / 2 - @width / 2, @spacer + Widget.margin, # TOP LEFT
window.width / 2 + @width / 2, @spacer + Widget.margin, # TOP RIGHT
window.width / 2 + @width / 2 - @slant, @spacer + Widget.margin + @height, # BOTTOM RIGHT
window.width / 2 - @width / 2 + @slant, @spacer + Widget.margin + @height, # BOTTOM LEFT
@color
)
@@ -31,10 +31,10 @@ class IMICFPS
# Current Health
fill_quad(
window.width / 2 - @width / 2, @spacer + Widget.vertical_margin, # TOP LEFT
(window.width / 2 - @width / 2) + @width * @health, @spacer + Widget.vertical_margin, # TOP RIGHT
bottom_right, @spacer + Widget.vertical_margin + @height, # BOTTOM RIGHT
window.width / 2 - @width / 2 + @slant, @spacer + Widget.vertical_margin + @height, # BOTTOM LEFT
window.width / 2 - @width / 2, @spacer + Widget.margin, # TOP LEFT
(window.width / 2 - @width / 2) + @width * @health, @spacer + Widget.margin, # TOP RIGHT
bottom_right, @spacer + Widget.margin + @height, # BOTTOM RIGHT
window.width / 2 - @width / 2 + @slant, @spacer + Widget.margin + @height, # BOTTOM LEFT
@shield
)
end
@@ -43,7 +43,7 @@ class IMICFPS
percentage = (@health * 100).round.to_s.rjust(3, "0")
@text.text = "[Health #{percentage}%]"
@text.x = window.width / 2 - @text.width / 2
@text.y = @spacer + Widget.vertical_margin + @height / 2 - @text.height / 2
@text.y = @spacer + Widget.margin + @height / 2 - @text.height / 2
@health += 0.1 * window.dt
@health = 0 if @health > 1.0

View File

@@ -12,41 +12,36 @@ class IMICFPS
@border_color = Gosu::Color.new(0x88c64600)
@radar_color = Gosu::Color.new(0x88212121)
@text = Text.new("RADAR", size: 18, font: MONOSPACE_FONT, border: true, border_color: Gosu::Color::BLACK)
@text = Text.new("RADAR", size: 18, mode: :add, font: MONOSPACE_FONT)
@image = Gosu::Image.new("#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/default.png", retro: true)
@scale = (@size - Widget.horizontal_padding * 2.0) / @image.width
@scale = (@size - Widget.padding * 2.0) / @image.width
end
def draw
Gosu.draw_rect(
Widget.horizontal_margin, window.height - (@size + Widget.vertical_margin),
Widget.margin, window.height - (@size + Widget.margin),
@size, @size,
@border_color
)
Gosu.draw_rect(
Widget.horizontal_margin + Widget.horizontal_padding,
window.height - (@size + Widget.vertical_margin) + Widget.vertical_padding,
@size - Widget.horizontal_padding * 2, @size - Widget.horizontal_padding * 2,
Widget.margin + Widget.padding, window.height - (@size + Widget.margin) + Widget.padding,
@size - Widget.padding * 2, @size - Widget.padding * 2,
@radar_color
)
@image.draw(
Widget.horizontal_margin + Widget.horizontal_padding,
window.height - (@size + Widget.vertical_margin) + Widget.vertical_padding,
46, @scale, @scale, 0x88ffffff
)
@image.draw(Widget.margin + Widget.padding, window.height - (@size + Widget.margin) + Widget.padding, 46, @scale, @scale, 0x88ffffff)
@text.draw
end
def update
@size = (window.width / @target_screen_width.to_f * @max_size).clamp(@min_size, @max_size)
@scale = (@size - Widget.horizontal_padding * 2.0) / @image.width
@scale = (@size - Widget.padding * 2.0) / @image.width
@text.text = "X: #{@player.position.x.round(1)} Y: #{@player.position.y.round(1)} Z: #{@player.position.z.round(1)}"
@text.x = Widget.horizontal_margin + @size / 2 - @text.width / 2
@text.y = window.height - (Widget.vertical_margin + @size + @text.height)
@text.x = Widget.margin + @size / 2 - @text.width / 2
@text.y = window.height - (Widget.margin + @size + @text.height)
end
end
end

View File

@@ -9,10 +9,13 @@ class IMICFPS
@text = CyberarmEngine::Text.new(
"",
size: 16,
x: Widget.horizontal_margin, y: Widget.vertical_margin, z: 45,
border: true,
border_color: Gosu::Color::BLACK,
font: BOLD_SANS_FONT
x: Widget.margin, y: Widget.margin, z: 45,
shadow: true,
shadow_size: 0.5,
shadow_alpha: 30,
shadow_color: Gosu::Color::WHITE,
mode: :add,
font: MONOSPACE_FONT
)
set_text
@@ -23,7 +26,7 @@ class IMICFPS
end
def update
@text.x = window.width - (@text.markup_width + Widget.horizontal_margin)
@text.x = window.width - (@text.markup_width + Widget.margin)
end
def generate_random_data

View File

@@ -14,10 +14,12 @@ class IMICFPS
@text = Text.new(
"MATE\nTinyTanker\nOther Player Dude\nHuman 0xdeadbeef",
size: 18,
mode: :add,
font: SANS_FONT,
color: @color,
border: true,
border_color: Gosu::Color::BLACK,
shadow: true,
shadow_color: 0x88000000,
shadow_size: 0.75
)
end
@@ -28,8 +30,8 @@ class IMICFPS
def update
@size = (window.width / @target_screen_width.to_f * @max_size).clamp(@min_size, @max_size)
@text.x = Widget.horizontal_margin + @size + Widget.horizontal_padding
@text.y = window.height - (Widget.vertical_margin + @text.height)
@text.x = Widget.margin + @size + Widget.padding
@text.y = window.height - (Widget.margin + @text.height)
end
end
end

View File

@@ -7,40 +7,37 @@ class IMICFPS
@masters = {}
@effects = []
@playlists = {}
@current_playlist = nil
@current_playlist_package = nil
@current_playlist_name = nil
@current_playlist_index = 0
def self.master_volume
window.config.get(:options, :audio, :volume_master)
1.0
end
def self.music_volume
window.config.get(:options, :audio, :volume_music) * master_volume
0.25 * master_volume
end
def self.sfx_volume
window.config.get(:options, :audio, :volume_sound_effects) * master_volume
0.5 * master_volume
end
def self.load_master(package)
return if @masters[package]
hash = JSON.parse(File.read("#{IMICFPS.assets_path}/#{package}/shared/sound/master.json"))
@masters[package] = hash
yaml = YAML.load_file("#{IMICFPS.assets_path}/#{package}/shared/sound/master.yaml")
@masters[package] = yaml
end
def self.sound(package, name)
raise "Missing sound: '#{name}' in package '#{package}'" unless (data = sound_data(package, name.to_s))
get_sample("#{IMICFPS.assets_path}/#{package}/shared/sound/#{data['path']}")
if data = sound_data(package, name.to_s)
get_sample("#{IMICFPS.assets_path}/#{package}/shared/sound/#{data['path']}")
else
raise "Missing sound: '#{name}' in package '#{package}'"
end
end
def self.sound_data(package, name)
load_master(package)
if (master = @masters[package])
if master = @masters[package]
return master["sounds"].find { |s| s["name"] == name }
end
@@ -51,61 +48,8 @@ class IMICFPS
@effects << klass.new(options)
end
def self.music(package, name)
raise "Missing song: '#{name}' in package '#{package}'" unless (data = music_data(package, name.to_s))
get_song("#{IMICFPS.assets_path}/#{package}/shared/sound/#{data['path']}")
end
def self.music_data(package, name)
load_master(package)
if (master = @masters[package])
return master["music"].find { |s| s["name"] == name }
end
nil
end
def self.playlist_data(package, name)
load_master(package)
if (master = @masters[package])
return master.dig("playlists", name.to_s)
end
nil
end
def self.play_playlist(package, name)
return if @current_playlist_name == name.to_s
return unless (list = playlist_data(package, name.to_s))
@current_playlist = list
@current_playlist_package = package
@current_playlist_name = name.to_s
@current_playlist_index = 0
@current_song = music(@current_playlist_package, @current_playlist[@current_playlist_index])
@current_song.volume = music_volume
@current_song.play
end
def self.update
@effects.each { |e| e.update; @effects.delete(e) if e.done? }
return unless @current_playlist
if !@current_song&.playing? && music_volume > 0.0
@current_playlist_index += 1
@current_playlist_index = 0 if @current_playlist_index >= @current_playlist.size
@current_song = music(@current_playlist_package, @current_playlist[@current_playlist_index])
@current_song.play
end
@current_song&.volume = music_volume
@current_song&.stop if music_volume < 0.1
end
end
end

View File

@@ -20,8 +20,8 @@ class IMICFPS
end
def setup
add_terrain if @map_parser.terrain.name
add_skybox if @map_parser.skydome.name
add_terrain
add_skybox
add_lights
add_entities

View File

@@ -40,7 +40,7 @@ class IMICFPS
@metadata.thumbnail = section["thumbnail"] # TODO: convert thumbnail to Image
@metadata.description = section["description"]
else
warn "Map metadata is missing!"
raise "Map metadata is missing!"
end
if section = data["terrain"]
@@ -64,7 +64,7 @@ class IMICFPS
end
@terrain.water_level = section["water_level"]
else
warn "Map terrain data is missing!"
raise "Map terrain data is missing!"
end
if section = data["skydome"]
@@ -87,7 +87,7 @@ class IMICFPS
@skydome.scale = Vector.new(1, 1, 1)
end
else
warn "Map skydome data is missing!"
raise "Map skydome data is missing!"
end
if section = data["lights"]
@@ -150,7 +150,7 @@ class IMICFPS
@entities << entity
end
else
warn "Map has no entities!"
raise "Map has no entities!"
end
if section = data["spawnpoints"]
@@ -171,7 +171,7 @@ class IMICFPS
@spawnpoints << spawnpoint
end
else
warn "Map has no spawnpoints!"
raise "Map has no spawnpoints!"
end
end

View File

@@ -7,7 +7,7 @@ class IMICFPS
Slot = Struct.new(:value, :width)
def initialize
@text = CyberarmEngine::Text.new("", x: 3, y: 3, border_color: Gosu::Color::BLACK)
@text = CyberarmEngine::Text.new("", x: 3, y: 3, shadow_color: Gosu::Color::BLACK)
@slots = []
@space_width = @text.textobject.text_width(" ")
end
@@ -38,8 +38,8 @@ class IMICFPS
end
if window.config.get(:debug_options, :stats)
create_slot "Vertices: #{formatted_number(window.renderer.opengl_renderer.number_of_vertices)}"
create_slot "Faces: #{formatted_number(window.renderer.opengl_renderer.number_of_vertices / 3)}"
create_slot "Vertices: #{formatted_number(window.number_of_vertices)}"
create_slot "Faces: #{formatted_number(window.number_of_vertices / 3)}"
end
if window.config.get(:debug_options, :boundingboxes)

View File

@@ -19,7 +19,7 @@ class IMICFPS
end
def calculate_volume
(SoundManager.sfx_volume - @initial_volume) * ratio
volume = (SoundManager.sfx_volume - @initial_volume) * ratio
end
def update

View File

@@ -3,50 +3,14 @@
class IMICFPS
class Boot < GameState
def setup
@title = Text.new(IMICFPS::NAME, size: 100, z: 0, color: Gosu::Color.new(0xff000000), border: false, font: IMICFPS::BOLD_SANS_FONT)
@title = Text.new(IMICFPS::NAME, size: 100, z: 0, color: Gosu::Color.new(0xff000000), shadow: false, font: "Droid Serif")
@logo = get_image("#{IMICFPS::GAME_ROOT_PATH}/static/logo.png")
@title_screen_sound = get_sample("#{IMICFPS::GAME_ROOT_PATH}/static/sounds/title_screen.ogg")
@start_time = Gosu.milliseconds
@time_to_live = 5_000
timing = Gosu.milliseconds
@animators = [
@fade_animator = CyberarmEngine::Animator.new(
start_time: timing,
duration: 500,
from: 0.0,
to: 1.0,
tween: :ease_in_out
),
@logo_animator = CyberarmEngine::Animator.new(
start_time: timing += 500,
duration: 500,
from: 0.0,
to: 1.0,
tween: :swing_to#:bounce_past
),
@title_animator = CyberarmEngine::Animator.new(
start_time: timing += 1_000,
duration: 500,
from: 0.0,
to: 1.0,
tween: :swing_to#:ease_out_circ
),
@arc_animator = CyberarmEngine::Animator.new(
start_time: timing += 500,
duration: 2_500,
from: 0.0,
to: 1.0,
tween: :ease_in_out
)
]
@time_to_live = 3_000
# SoundManager.sound_effect(SoundEffect::FadeIn, sound: SoundManager.sound("base", :shield_regen), duration: 3_000.0)
window.needs_cursor = false
@title_screen_sound_channel = @title_screen_sound.play(1.0)
end
def draw
@@ -55,45 +19,39 @@ class IMICFPS
menu_background(Menu::PRIMARY_COLOR, Menu::ACCENT_COLOR, Menu::BAR_COLOR_STEP, Menu::BAR_ALPHA, Menu::BAR_SIZE, Menu::BAR_SLOPE)
if fraction_left <= 1.0
Gosu.scale(@logo_animator.transition, @logo_animator.transition, window.width / 2, window.height / 2) do
Gosu.draw_circle(
window.width / 2,
window.height / 2,
@logo.width / 2, 128, Gosu::Color.new(0x11ffffff)
)
Gosu.draw_circle(
window.width / 2,
window.height / 2,
@logo.width / 2, 128, Gosu::Color.new(0x11ffffff)
)
Gosu.draw_arc(
window.width / 2,
window.height / 2,
@logo.width / 2, @arc_animator.transition, 128, 8, Gosu::Color.new(0x33ff8800)
)
Gosu.draw_arc(
window.width / 2,
window.height / 2,
@logo.width / 2, fraction_left.clamp(0.0, 1.0), 128, 8, Gosu::Color.new(0x33ff8800)
)
@logo.draw(window.width / 2 - @logo.width / 2, window.height / 2 - @logo.height / 2, 0)
end
@logo.draw(window.width / 2 - @logo.width / 2, window.height / 2 - @logo.height / 2, 0)
@title.draw
fill(Gosu::Color.rgba(0, 0, 0, 255 * (1 - @fade_animator.transition)))
fill(Gosu::Color.rgba(0, 0, 0, 255 * (1.2 - fraction_left)))
end
end
def update
@animators.each(&:update)
y = window.height / 2 - (@logo.height / 2 + @title.height + 8)
y = 0 if y < @title.height
@title.x = window.width / 2 - @title.width / 2
@title.y = (0 - (@title.height * (1 - @title_animator.transition))) + (y * @title_animator.transition)
@title.y = 0
push_state(MainMenu) if Gosu.milliseconds - @start_time >= @time_to_live
end
def button_down(id)
super
@title_screen_sound_channel.stop
push_state(MainMenu)
def button_up(id)
if (id == Gosu::KbEscape) ||
((id >= Gosu::GP_LEFT) && (id >= Gosu::GP_BUTTON_15)) ||
(id == Gosu::MsLeft)
push_state(MainMenu)
end
end
end
end

View File

@@ -3,63 +3,41 @@
class IMICFPS
class Close < GameState
def setup
@slope = Menu::BAR_SLOPE
@logo = get_image("#{IMICFPS::GAME_ROOT_PATH}/static/logo.png")
@start_time = Gosu.milliseconds
@time_to_live = 3_750
timing = Gosu.milliseconds
@animators = [
@arc_animator = CyberarmEngine::Animator.new(
start_time: timing += 0,
duration: 2_500,
from: 1.0,
to: 0.0,
tween: :ease_in_out
),
@logo_animator = CyberarmEngine::Animator.new(
start_time: timing += 2_500,
duration: 500,
from: 1.0,
to: 0.0,
tween: :swing_from
),
@fade_animator = CyberarmEngine::Animator.new(
start_time: timing += 500,
duration: 500,
from: 0.0,
to: 1.0,
tween: :ease_in_out
),
]
@time_to_live = 3_000
window.needs_cursor = false
end
def draw
menu_background(Menu::PRIMARY_COLOR, Menu::ACCENT_COLOR, Menu::BAR_COLOR_STEP, Menu::BAR_ALPHA, Menu::BAR_SIZE, Menu::BAR_SLOPE)
fraction_left = 1 - ((Gosu.milliseconds - @start_time) / (@time_to_live - 200).to_f)
Gosu.scale(@logo_animator.transition, @logo_animator.transition, window.width / 2, window.height / 2) do
Gosu.draw_circle(
window.width / 2,
window.height / 2,
@logo.width / 2, 128, Gosu::Color.new(0x11ffffff)
)
menu_background(Menu::PRIMARY_COLOR, Menu::ACCENT_COLOR, Menu::BAR_COLOR_STEP, Menu::BAR_ALPHA, Menu::BAR_SIZE, @slope.round)
Gosu.draw_arc(
window.width / 2,
window.height / 2,
@logo.width / 2, @arc_animator.transition, 128, 8, Gosu::Color.new(0x33ff8800)
)
Gosu.draw_circle(
window.width / 2,
window.height / 2,
@logo.width / 2, 128, Gosu::Color.new(0x11ffffff)
)
@logo.draw(window.width / 2 - @logo.width / 2, window.height / 2 - @logo.height / 2, 0)
end
Gosu.draw_arc(
window.width / 2,
window.height / 2,
@logo.width / 2, fraction_left.clamp(0.0, 1.0), 128, 8, Gosu::Color.new(0x33ff8800)
)
fill(Gosu::Color.rgba(0, 0, 0, 255 * @fade_animator.transition))
@logo.draw(window.width / 2 - @logo.width / 2, window.height / 2 - @logo.height / 2, 0)
fill(Gosu::Color.rgba(0, 0, 0, 255 * (1.1 - fraction_left)))
end
def update
window.close! if Gosu.milliseconds - @start_time >= @time_to_live
@slope -= 25 * window.dt
end
def button_up(id)

View File

@@ -27,7 +27,7 @@ class IMICFPS
def draw
window.director.map.render(@camera)
@hud.draw if window.config.get(:options, :hud)
@hud.draw
end
def update
@@ -69,8 +69,6 @@ class IMICFPS
window.director.map.entities.each do |entity|
entity.button_down(id) if defined?(entity.button_down)
end
@hud.button_down(id)
end
def button_up(id)
@@ -83,8 +81,6 @@ class IMICFPS
window.director.map.entities.each do |entity|
entity.button_up(id) if defined?(entity.button_up)
end
@hud.button_up(id)
end
end
end

View File

@@ -22,8 +22,8 @@ class IMICFPS
@assets = []
@asset_index = 0
add_asset(:model, @map_parser.terrain.package, @map_parser.terrain.name) if @map_parser.terrain.package
add_asset(:model, @map_parser.skydome.package, @map_parser.skydome.name) if @map_parser.skydome.package
add_asset(:model, @map_parser.terrain.package, @map_parser.terrain.name)
add_asset(:model, @map_parser.skydome.package, @map_parser.skydome.name)
@map_parser.entities.each do |entity|
add_asset(:model, entity.package, entity.name)
end

View File

@@ -17,19 +17,17 @@ class IMICFPS
@manifests.sort_by! { |m| m.name.downcase }
label IMICFPS::NAME.to_s, text_size: 100, color: Gosu::Color::BLACK
label "Asset Viewer", text_size: 50
flow(width: 1.0, height: 1.0) do
stack(width: 0.25, height: 1.0) do
button "Back", width: 1.0 do
pop_state
end
end
stack(width: 0.5, height: 1.0) do
title "Asset Viewer"
link I18n.t("menus.back"), width: 1.0 do
pop_state
end
flow(width: 1.0, height: 1.0) do
@manifests.each do |manifest|
button manifest.name do

View File

@@ -3,14 +3,16 @@
class IMICFPS
class AssetViewerTool
class TurnTable < CyberarmEngine::GuiState
include LightManager
attr_reader :map
def setup
window.needs_cursor = false
@manifest = @options[:manifest]
window.director.load_map(map_parser: MapParser.new(map_file: "#{GAME_ROOT_PATH}/maps/model_viewer.json"))
@map = window.director.map
@map = ProtoMap.new
Publisher.new
@entity = Entity.new(manifest: @manifest)
@entity.bind_model
@@ -18,15 +20,16 @@ class IMICFPS
@map.entities.each { |e| e.backface_culling = false }
@crosshair = Crosshair.new(color: Gosu::Color.rgba(100, 200, 255, 100))
@map.add_light @light = Light.new(type: Light::DIRECTIONAL, id: @map.available_light, position: Vector.new, diffuse: Vector.new(1, 1, 1, 1))
@lights = []
@light = Light.new(type: Light::DIRECTIONAL, id: available_light, position: Vector.new, diffuse: Vector.new(1, 1, 1, 1))
@lights << @light
@camera = PerspectiveCamera.new(aspect_ratio: window.aspect_ratio, position: Vector.new(0, 1.5, 5), orientation: Vector.forward)
@camera_controller = CameraController.new(camera: @camera, entity: nil, mode: :fpv)
label @manifest.name, text_size: 50, text_border: true, text_border_color: Gosu::Color::BLACK
label @manifest.model, text_border: true, text_border_color: Gosu::Color::BLACK
@camera_position = label "", text_border: true, text_border_color: Gosu::Color::BLACK
@camera_orientation = label "", text_border: true, text_border_color: Gosu::Color::BLACK
label @manifest.name, text_size: 50
label @manifest.model
@camera_position = label ""
@camera_orientation = label ""
button "Back" do
pop_state
@@ -45,7 +48,7 @@ class IMICFPS
)
Gosu.gl do
window.renderer.draw(@camera, @map.lights, @map.entities)
window.renderer.draw(@camera, [@light], @map.entities)
end
@crosshair.draw
@@ -61,24 +64,30 @@ class IMICFPS
@camera_position.value = "Camera Position: X #{@camera.position.x.round(2)}, Y #{@camera.position.y.round(2)}, Z #{@camera.position.z.round(2)}"
@camera_orientation.value = "Camera Orientation: X #{@camera.orientation.x.round(2)}, Y #{@camera.orientation.y.round(2)}, Z #{@camera.orientation.z.round(2)}\nEntities: #{@map.entities.count}"
@camera_controller.free_move
@camera_controller.update
# @map.entities.each(&:update)
@map.entities.each(&:update)
end
def button_down(id)
super
InputMapper.keydown(id)
@camera_controller.button_down(id)
end
def button_up(id)
super
InputMapper.keyup(id)
@camera_controller.button_up(id)
end
end
# Stub for enabling scripted models to load properly
class ProtoMap
include EntityManager
attr_reader :entities
def initialize
@entities = []
end
end
end

View File

@@ -6,6 +6,9 @@ class IMICFPS
def setup
window.needs_cursor = true
label IMICFPS::NAME.to_s, text_size: 50
label "Map Editor", text_size: 28
@maps = []
Dir.glob("#{GAME_ROOT_PATH}/maps/*.json").each do |map|
begin
@@ -19,20 +22,15 @@ class IMICFPS
flow(width: 1.0, height: 1.0) do
stack(width: 0.25, height: 1.0) do
button "New Map", width: 1.0
button "Back", margin_top: 64, width: 1.0 do
pop_state
end
end
stack(width: 0.5, height: 1.0) do
title "Map Editor"
flow width: 1.0 do
link I18n.t("menus.back"), width: 0.32 do
pop_state
end
button "New Map", width: 0.64
end
label "Edit Map", width: 1.0, text_align: :center, text_size: 50
label "Edit Map"
flow(width: 1.0, height: 1.0) do
@maps.each do |map|
button map.metadata.name do

View File

@@ -25,7 +25,11 @@ class IMICFPS
end
def insert_leaf(leaf)
@root = @root ? @root.insert_subtree(leaf) : leaf
@root = if @root
@root.insert_subtree(leaf)
else
leaf
end
end
def update(object, bounding_box)

158
lib/ui/command.rb Normal file
View File

@@ -0,0 +1,158 @@
# frozen_string_literal: true
class IMICFPS
class Commands
module Style
def self.error(string)
"<c=ff5555>#{string}</c>"
end
def self.warn(string)
"<c=ff7700>#{string}</c>"
end
def self.notice(string)
"<c=55ff55>#{string}</c>"
end
def self.highlight(string, color = "5555ff")
"<c=#{color}>#{string}</c>"
end
end
class Command
def self.inherited(subclass)
@list ||= []
@commands ||= []
@list << subclass
end
def self.setup
@list ||= []
@commands = []
@list.each do |subclass|
cmd = subclass.new
if @commands.detect { |c| c.command == cmd.command }
raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
end
@commands << cmd
end
end
def self.use(command, arguments, console)
found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
if found_command
found_command.handle(arguments, console)
else
console.stdin("Command #{Style.error(command)} not found.")
end
end
def self.find(command)
@commands.detect { |cmd| cmd.command == command.to_sym }
end
def self.list_commands
@commands
end
def initialize
@store = {}
@subcommands = []
setup
end
def setup
end
def subcommand(command, type)
if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
raise "Subcommand '#{command}' for '#{self.command}' already exists!"
end
@subcommands << SubCommand.new(self, command, type)
end
def get(key)
@store[key]
end
def set(key, value)
@store[key] = value
end
def group
raise NotImplementedError
end
def command
raise NotImplementedError
end
def handle(arguments, console)
raise NotImplementedError
end
def autocomplete(console)
split = console.text_input.text.split(" ")
if @subcommands.size.positive?
if !console.text_input.text.end_with?(" ") && split.size == 2
list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{list.first} "
else
return unless list.size.positive?
console.stdin(list.map { |cmd| Commands::Style.highlight(cmd) }.join(", ").to_s)
end
# List available options on subcommand
elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
if subcommand
if split.size == 2
console.stdin("Available options: #{subcommand.values.map { |value| Commands::Style.highlight(value) }.join(',')}")
else
list = console.abbrev_search(subcommand.values, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
elsif list.size.positive?
console.stdin("Available options: #{list.map { |value| Commands::Style.highlight(value) }.join(',')}")
end
end
end
# List available subcommands if command was entered and has only a space after it
elsif console.text_input.text.end_with?(" ") && split.size == 1
console.stdin("Available subcommands: #{@subcommands.map { |cmd| Commands::Style.highlight(cmd.command) }.join(', ')}")
end
end
end
def handle_subcommand(arguments, console)
if arguments.size.zero?
console.stdin(usage)
return
end
subcommand = arguments.delete_at(0)
found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
if found_command
found_command.handle(arguments, console)
else
console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
end
end
def usage
raise NotImplementedError
end
end
end
end

View File

@@ -2,7 +2,7 @@
class IMICFPS
class Commands
class ConnectCommand < CyberarmEngine::Console::Command
class ConnectCommand < Command
def group
:global
end
@@ -15,7 +15,7 @@ class IMICFPS
end
def usage
"Connect to a server.\n#{Console::Style.highlight('connect')} #{Style.notice('example.com[:56789]')}"
"Connect to a server.\n#{Style.highlight('connect')} #{Style.notice('[example.com:56789]')}"
end
end
end

View File

@@ -2,7 +2,7 @@
class IMICFPS
class Commands
class DebugCommand < CyberarmEngine::Console::Command
class DebugCommand < Command
def group
:global
end

View File

@@ -2,7 +2,7 @@
class IMICFPS
class Commands
class DisconnectCommand < CyberarmEngine::Console::Command
class DisconnectCommand < Command
def group
:global
end

View File

@@ -2,7 +2,7 @@
class IMICFPS
class Commands
class FPSCommand < CyberarmEngine::Console::Command
class FPSCommand < Command
def group
:global
end
@@ -13,26 +13,26 @@ class IMICFPS
def handle(arguments, console)
if arguments.size > 1
console.stdin("to many arguments for #{Console::Style.highlight(command.to_s)}, got #{Console::Style.error(arguments.size)} expected #{Console::Style.notice(1)}.")
console.stdin("to many arguments for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.size)} expected #{Style.notice(1)}.")
return
end
case arguments.last
when "", nil
console.stdin("#{Console::Style.highlight('fps')}: #{$window.config.get(:options, :fps)}")
console.stdin("#{Style.highlight('fps')}: #{$window.config.get(:options, :fps)}")
when "on"
var = $window.config[:options, :fps] = true
console.stdin("fps => #{Console::Style.highlight(var)}")
console.stdin("fps => #{Style.highlight(var)}")
when "off"
var = $window.config[:options, :fps] = false
console.stdin("fps => #{Console::Style.highlight(var)}")
console.stdin("fps => #{Style.highlight(var)}")
else
console.stdin("Invalid argument for #{Console::Style.highlight(command.to_s)}, got #{Console::Style.error(arguments.last)} expected #{Console::Style.notice('on')}, or #{Console::Style.notice('off')}.")
console.stdin("Invalid argument for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.last)} expected #{Style.notice('on')}, or #{Style.notice('off')}.")
end
end
def usage
"#{Console::Style.highlight('fps')} #{Style.notice('[on|off]')}"
"#{Style.highlight('fps')} #{Style.notice('[on|off]')}"
end
end
end

View File

@@ -0,0 +1,43 @@
# frozen_string_literal: true
class IMICFPS
class Commands
class HelpCommand < Command
def group
:global
end
def command
:help
end
def handle(arguments, console)
console.stdin(usage(arguments.first))
end
def autocomplete(console)
split = console.text_input.text.split(" ")
if !console.text_input.text.start_with?(" ") && split.size == 2
list = console.abbrev_search(Commands::Command.list_commands.map { |cmd| cmd.command.to_s }, split.last)
if list.size == 1
console.text_input.text = "#{split.first} #{list.first} "
elsif list.size > 1
console.stdin(list.map { |cmd| Style.highlight(cmd) }.join(", "))
end
end
end
def usage(command = nil)
if command
if cmd = Command.find(command)
cmd.usage
else
"#{Style.error(command)} is not a command"
end
else
"Available commands:\n#{Command.list_commands.map { |cmd| Style.highlight(cmd.command).to_s }.join(', ')}"
end
end
end
end
end

View File

@@ -1,43 +0,0 @@
# frozen_string_literal: true
class IMICFPS
class Commands
class HUDCommand < CyberarmEngine::Console::Command
def group
:global
end
def command
:hud
end
def setup
$window.config[:options, :hud] = true if $window.config.get(:options, :hud).nil?
end
def handle(arguments, console)
if arguments.size > 1
console.stdin("to many arguments for #{Console::Style.highlight(command.to_s)}, got #{Console::Style.error(arguments.size)} expected #{Console::Style.notice(1)}.")
return
end
case arguments.last
when "", nil
console.stdin("#{Console::Style.highlight(command.to_s)}: #{$window.config.get(:options, command)}")
when "on"
var = $window.config[:options, command] = true
console.stdin("#{command} => #{Console::Style.highlight(var)}")
when "off"
var = $window.config[:options, command] = false
console.stdin("#{command} => #{Console::Style.highlight(var)}")
else
console.stdin("Invalid argument for #{Console::Style.highlight(command.to_s)}, got #{Console::Style.error(arguments.last)} expected #{Console::Style.notice('on')}, or #{Console::Style.notice('off')}.")
end
end
def usage
"#{Console::Style.highlight('hud')} #{Console::Style.notice('[on|off]')}"
end
end
end
end

View File

@@ -2,7 +2,7 @@
class IMICFPS
class Commands
class ReloadShaderCommand < CyberarmEngine::Console::Command
class ReloadShaderCommand < Command
def group
:reload_shader
end
@@ -13,7 +13,7 @@ class IMICFPS
def handle(arguments, console)
if arguments.size > 2
console.stdin("to many arguments for #{Console::Style.highlight(command.to_s)}, got #{Console::Style.error(arguments.size)} expected #{Console::Style.notice(1)}.")
console.stdin("to many arguments for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.size)} expected #{Style.notice(1)}.")
return
end
@@ -51,9 +51,9 @@ class IMICFPS
string = $stdout.string
if shader.compiled?
console.stdin("#{Console::Style.notice('Successfully reloaded shader')}: #{shader.name}")
console.stdin("#{Style.notice('Successfully reloaded shader')}: #{shader.name}")
else
console.stdin(Console::Style.error("Failed to reload #{shader.name}").to_s)
console.stdin(Style.error("Failed to reload #{shader.name}").to_s)
console.stdin(string)
end
ensure
@@ -62,7 +62,7 @@ class IMICFPS
end
def usage
"#{Console::Style.highlight(command)} #{Console::Style.notice('vertex_name [fragment_name]')}"
"#{Style.highlight(command)} #{Style.notice('vertex_name [fragment_name]')}"
end
end
end

View File

@@ -2,7 +2,7 @@
class IMICFPS
class Commands
class RendererInfoCommand < CyberarmEngine::Console::Command
class RendererInfoCommand < Command
def group
:global
end
@@ -12,14 +12,14 @@ class IMICFPS
end
def handle(_arguments, console)
console.stdin("OpenGL Vendor: #{Console::Style.notice(glGetString(GL_VENDOR))}")
console.stdin("OpenGL Renderer: #{Console::Style.notice(glGetString(GL_RENDERER))}")
console.stdin("OpenGL Version: #{Console::Style.notice(glGetString(GL_VERSION))}")
console.stdin("OpenGL Shader Language Version: #{Console::Style.notice(glGetString(GL_SHADING_LANGUAGE_VERSION))}")
console.stdin("OpenGL Vendor: #{Style.notice(glGetString(GL_VENDOR))}")
console.stdin("OpenGL Renderer: #{Style.notice(glGetString(GL_RENDERER))}")
console.stdin("OpenGL Version: #{Style.notice(glGetString(GL_VERSION))}")
console.stdin("OpenGL Shader Language Version: #{Style.notice(glGetString(GL_SHADING_LANGUAGE_VERSION))}")
end
def usage
"#{Console::Style.highlight('renderer_info')} #{Console::Style.notice('Returns OpenGL renderer information')}"
"#{Style.highlight('renderer_info')} #{Style.notice('Returns OpenGL renderer information')}"
end
end
end

250
lib/ui/console.rb Normal file
View File

@@ -0,0 +1,250 @@
# frozen_string_literal: true
class IMICFPS
class Console
Z = 100_000
PADDING = 2
include CommonMethods
attr_reader :text_input
def initialize
@text_input = Gosu::TextInput.new
@width = window.width / 4 * 3
@height = window.height / 4 * 3
@input = Text.new("", x: 4, y: @height - (PADDING * 2), z: Console::Z + 1, font: MONOSPACE_FONT)
@input.y -= @input.height
@history = Text.new("=== #{IMICFPS::NAME} v#{IMICFPS::VERSION} (#{IMICFPS::RELEASE_NAME}) ===\n\n", x: 4, z: Console::Z + 1, font: MONOSPACE_FONT)
update_history_y
@command_history = []
@command_history_index = 0
@memory = ""
@background_color = Gosu::Color.rgba(0, 0, 0, 200)
@foreground_color = Gosu::Color.rgba(100, 100, 100, 100)
@input_color = Gosu::Color.rgba(100, 100, 100, 200)
@showing_cursor = false
@active_text_input = nil
@show_caret = true
@caret_last_change = Gosu.milliseconds
@caret_interval = 250
@caret_color = Gosu::Color::WHITE
@selection_color = Gosu::Color.new(0x5522ff22)
end
def draw
# Background/Border
draw_rect(0, 0, @width, @height, @background_color, Console::Z)
# Foregound/History
draw_rect(PADDING, PADDING, @width - (PADDING * 2), @height - (PADDING * 2), @foreground_color, Console::Z)
# Text bar
draw_rect(2, @input.y, @width - (PADDING * 2), @input.height, @input_color, Console::Z)
@history.draw
@input.draw
# Caret
if @show_caret
draw_rect(@input.x + caret_from_left, @input.y, Console::PADDING, @input.height, @caret_color, Console::Z + 2)
end
# Caret selection
if caret_start != caret_end
if caret_start < @text_input.selection_start
draw_rect(@input.x + caret_from_left, @input.y, caret_selection_width, @input.height, @selection_color, Console::Z)
else
draw_rect((@input.x + caret_from_left) - caret_selection_width, @input.y, caret_selection_width, @input.height, @selection_color, Console::Z)
end
end
end
def caret_from_left
return 0 if @text_input.caret_pos.zero?
@input.textobject.text_width(@text_input.text[0..@text_input.caret_pos - 1])
end
def caret_selection_width
@input.textobject.text_width(@text_input.text[caret_start..(caret_end - 1)])
end
def caret_pos
@text_input.caret_pos
end
def caret_start
@text_input.selection_start < @text_input.caret_pos ? @text_input.selection_start : @text_input.caret_pos
end
def caret_end
@text_input.selection_start > @text_input.caret_pos ? @text_input.selection_start : @text_input.caret_pos
end
def update
if Gosu.milliseconds - @caret_last_change >= @caret_interval
@caret_last_change = Gosu.milliseconds
@show_caret = !@show_caret
end
if @width != window.width || @height != @height
@width = window.width / 4 * 3
@height = window.height / 4 * 3
@input.y = @height - (PADDING * 2 + @input.height)
update_history_y
end
@input.text = @text_input.text
end
def button_down(id)
case id
when Gosu::KbEnter, Gosu::KbReturn
return unless @text_input.text.length.positive?
@history.text += "\n<c=999999>> #{@text_input.text}</c>"
@command_history << @text_input.text
@command_history_index = @command_history.size
update_history_y
handle_command
@text_input.text = ""
when Gosu::KbUp
@command_history_index -= 1
@command_history_index = 0 if @command_history_index.negative?
@text_input.text = @command_history[@command_history_index]
when Gosu::KbDown
@command_history_index += 1
if @command_history_index > @command_history.size - 1
@text_input.text = "" unless @command_history_index > @command_history.size
@command_history_index = @command_history.size
else
@text_input.text = @command_history[@command_history_index]
end
when Gosu::KbTab
split = @text_input.text.split(" ")
if !@text_input.text.end_with?(" ") && split.size == 1
list = abbrev_search(Commands::Command.list_commands.map { |cmd| cmd.command.to_s }, @text_input.text)
if list.size == 1
@text_input.text = "#{list.first} "
elsif list.size.positive?
stdin("\n#{list.map { |cmd| Commands::Style.highlight(cmd) }.join(', ')}")
end
elsif split.size.positive? && cmd = Commands::Command.find(split.first)
cmd.autocomplete(self)
end
when Gosu::KbBacktick
# Remove backtick character from input
@text_input.text = if @text_input.text.size > 1
@text_input.text[0..@text_input.text.size - 2]
else
""
end
# Copy
when Gosu::KbC
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
p @memory
elsif control_down?
@text_input.text = ""
end
# Paste
when Gosu::KbV
if control_down? && shift_down?
string = @text_input.text.chars.insert(caret_pos, @memory).join
_caret_pos = caret_pos
@text_input.text = string
@text_input.caret_pos = _caret_pos + @memory.length
@text_input.selection_start = _caret_pos + @memory.length
end
# Cut
when Gosu::KbX
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
string = @text_input.text.chars
Array(caret_start..caret_end - 1).each_with_index do |i, j|
string.delete_at(i - j)
end
@text_input.text = string.join
end
# Delete word to left of caret
when Gosu::KbW
if control_down?
split = @text_input.text.split(" ")
split.delete(split.last)
@text_input.text = split.join(" ")
end
# Clear history
when Gosu::KbL
@history.text = "" if control_down?
end
end
def button_up(id)
end
def update_history_y
@history.y = @height - (PADDING * 2) - @input.height - (@history.text.lines.count * @history.textobject.height)
end
def handle_command
string = @text_input.text
split = string.split(" ")
command = split.first
arguments = split.length.positive? ? split[1..split.length - 1] : []
IMICFPS::Commands::Command.use(command, arguments, self)
end
def abbrev_search(array, text)
return [] unless text.length.positive?
list = []
Abbrev.abbrev(array).each do |abbrev, value|
next unless abbrev&.start_with?(text)
list << value
end
list.uniq
end
def stdin(string)
@history.text += "\n#{string}"
update_history_y
end
def focus
InputMapper.reset_keys
@active_text_input = window.text_input
window.text_input = @text_input
@showing_cursor = window.needs_cursor
window.needs_cursor = true
@show_caret = true
@caret_last_change = Gosu.milliseconds
end
def blur
window.text_input = @active_text_input
window.needs_cursor = @showing_cursor
end
end
end

View File

@@ -22,9 +22,7 @@ class IMICFPS
@accent_color = ACCENT_COLOR
window.needs_cursor = true
@__version_text = CyberarmEngine::Text.new(
"<b>#{IMICFPS::NAME}</b> v#{IMICFPS::VERSION} (#{IMICFPS::RELEASE_NAME})",
font: BOLD_SANS_FONT, border: true, border_color: Gosu::Color::BLACK)
@__version_text = CyberarmEngine::Text.new("<b>#{IMICFPS::NAME}</b> v#{IMICFPS::VERSION} (#{IMICFPS::RELEASE_NAME})", font: BOLD_SANS_FONT)
@__version_text.x = window.width - (@__version_text.width + 10)
@__version_text.y = window.height - (@__version_text.height + 10)
@@ -41,9 +39,7 @@ class IMICFPS
text_size: 100,
color: Gosu::Color::BLACK,
text_align: :center,
text_shadow: true,
text_shadow_size: 4,
width: 1.0,
width: 1.0
},
Subtitle: {
text_size: 50,
@@ -55,10 +51,10 @@ class IMICFPS
font: BOLD_SANS_FONT,
text_size: 50,
text_align: :center,
text_border: true,
text_border_size: 2,
text_border_color: Gosu::Color::BLACK,
text_border_alpha: 100,
text_shadow: true,
text_shadow_size: 2,
text_shadow_color: Gosu::Color::BLACK,
text_shadow_alpha: 100,
color: Gosu::Color.rgb(0, 127, 127),
width: 1.0,
hover: {
@@ -66,10 +62,7 @@ class IMICFPS
},
active: {
color: Gosu::Color.rgb(64, 128, 255),
},
disabled: {
color: Gosu::Color.rgb(175, 175, 175),
},
}
},
Button:
{
@@ -93,8 +86,6 @@ class IMICFPS
}
}
)
SoundManager.play_playlist("base", "menus")
end
def draw

View File

@@ -3,7 +3,8 @@
class IMICFPS
class ExtrasMenu < Menu
def setup
title I18n.t("menus.extras")
title IMICFPS::NAME
subtitle "Extras"
link "Asset Viewer" do
push_state(IMICFPS::AssetViewerTool::MainMenu)
@@ -13,7 +14,7 @@ class IMICFPS
push_state(IMICFPS::MapEditorTool::MainMenu)
end
link I18n.t("menus.back"), margin_top: 25 do
link I18n.t("menus.back") do
pop_state
end
end

View File

@@ -4,7 +4,8 @@ class IMICFPS
class GamePauseMenu < Menu
def setup
@bar_alpha = 50
title "Paused"
title IMICFPS::NAME
subtitle "Paused"
link "Resume" do
pop_state
@@ -14,7 +15,7 @@ class IMICFPS
push_state(SettingsMenu)
end
link I18n.t("menus.leave"), margin_top: 25 do
link I18n.t("menus.leave") do
push_state(MainMenu)
end
end

View File

@@ -3,12 +3,10 @@
class IMICFPS
class LevelSelectMenu < Menu
def setup
title I18n.t("menus.singleplayer")
title IMICFPS::NAME
subtitle "Choose a Map"
Dir.glob("#{GAME_ROOT_PATH}/maps/*.json").map { |file| [file, MapParser.new(map_file: file)] }.each do |file, map|
next unless map.metadata.gamemode
link map.metadata.name do
push_state(
LoadingState.new(forward: Game, map_file: file)
@@ -16,7 +14,7 @@ class IMICFPS
end
end
link I18n.t("menus.back"), margin_top: 25 do
link I18n.t("menus.back") do
pop_state
end
end

View File

@@ -6,7 +6,7 @@ class IMICFPS
title IMICFPS::NAME
link I18n.t("menus.singleplayer") do
push_state(SinglePlayerMenu)
push_state(LevelSelectMenu)
end
link I18n.t("menus.multiplayer") do
@@ -21,7 +21,7 @@ class IMICFPS
push_state(ExtrasMenu)
end
link I18n.t("menus.quit"), margin_top: 25 do
link I18n.t("menus.quit") do
window.close
end
@@ -40,17 +40,14 @@ Fallback <b>immediate mode renderer</b> will be used."
(Linux Only) For MESA based drivers append <b>--mesa-override</b>
as a commandline argument to override reported version."
message += linux_mesa_message if RUBY_PLATFORM =~ /linux/ && gl_version.downcase.include?(" mesa ")
@old_gl_warning = Text.new(message, size: 24, z: Float::INFINITY, border: true, border_color: Gosu::Color::BLACK)
@old_gl_warning = Gosu::Image.from_markup(message, 24, align: :center, font: "")
end
end
def draw
super
@old_gl_warning&.x = window.width / 2 - @old_gl_warning.width / 2
@old_gl_warning&.y = window.height - (@old_gl_warning.height + 10)
@old_gl_warning&.draw
@old_gl_warning&.draw(window.width / 2 - @old_gl_warning.width / 2, window.height - (@old_gl_warning.height + 10), Float::INFINITY)
end
end
end

View File

@@ -3,7 +3,8 @@
class IMICFPS
class MultiplayerMenu < Menu
def setup
title I18n.t("menus.multiplayer")
title IMICFPS::NAME
subtitle "Multiplayer"
link "Quick Join"
link "Server Browser" do
@@ -12,7 +13,7 @@ class IMICFPS
link "Profile" do
push_state(MultiplayerProfileMenu)
end
link I18n.t("menus.back"), margin_top: 25 do
link I18n.t("menus.back") do
pop_state
end
end

View File

@@ -3,22 +3,17 @@
class IMICFPS
class MultiplayerProfileMenu < Menu
def setup
label "Profile", text_size: 100, color: Gosu::Color::BLACK
flow(width: 1.0, height: 1.0) do
stack(width: 0.25, height: 1.0) do
end
stack(width: 0.5, height: 1.0) do
title "Profile"
flow width: 1.0 do
link I18n.t("menus.back"), width: 0.333 do
pop_state
end
button "Edit Profile", width: 0.333
button "Log Out", width: 0.333
button "Edit Profile", width: 1.0
button "Log Out", width: 1.0
button I18n.t("menus.back"), width: 1.0, margin_top: 64 do
pop_state
end
end
stack(width: 0.5, height: 1.0) do
flow(width: 1.0, padding: 4) do
background 0x88_222222
@@ -30,7 +25,7 @@ class IMICFPS
end
end
flow(margin_top: 4) do
flow(margin_top: 4, margin_right: 4) do
stack do
label "Kiil/Death Ratio"
label "Kills"
@@ -41,8 +36,8 @@ class IMICFPS
label "Repair Points"
end
stack margin_left: 16 do
label "0.75"
stack do
label "0.72"
label "21"
label "28"
label "14"

View File

@@ -30,47 +30,41 @@ class IMICFPS
}
]
flow(width: 1.0, height: 1.0) do
label "Server Browser", text_size: 100
flow width: 1.0, height: 1.0 do
stack width: 0.25 do
button "Host Game", width: 1.0
button "Direct Connect", width: 1.0
button I18n.t("menus.back"), width: 1.0, margin_top: 64 do
pop_state
end
end
stack width: 0.5, height: 1.0 do
stack width: 1.0, height: 0.25 do
title "Server Browser"
flow(width: 1.0) do
link I18n.t("menus.back"), width: 0.333 do
pop_state
end
button "Host Game", width: 0.333
button "Direct Connect", width: 0.333
end
end
stack width: 1.0, height: 0.5, border_color: 0xffffffff, border_thickness: 1 do
stack width: 1.0, height: 0.75, border_color: 0xffffffff, border_thickness: 1 do
@sample_games.each_with_index do |game, i|
text_size = 18
flow width: 1.0 do
background i.even? ? 0x55000000 : 0x55ff5500
flow width: 0.1 do
label game[:game_type], text_size: text_size, text_wrap: :none, font: i.zero? ? BOLD_SANS_FONT : SANS_FONT
label game[:game_type], text_size: text_size
end
flow width: 0.3 do
label game[:host], text_size: text_size, text_wrap: :none, font: i.zero? ? BOLD_SANS_FONT : SANS_FONT
label game[:host], text_size: text_size
end
flow width: 0.3 do
label game[:map], text_size: text_size, text_wrap: :none, font: i.zero? ? BOLD_SANS_FONT : SANS_FONT
label game[:map], text_size: text_size
end
flow width: 0.1 do
label game[:players], text_size: text_size, text_wrap: :none, font: i.zero? ? BOLD_SANS_FONT : SANS_FONT
label game[:players], text_size: text_size
end
flow width: 0.1 do
label game[:ping], text_size: text_size, text_wrap: :none, font: i.zero? ? BOLD_SANS_FONT : SANS_FONT
label game[:ping], text_size: text_size
end
flow width: 0.1 do
label game[:source], text_size: text_size, text_wrap: :none, font: i.zero? ? BOLD_SANS_FONT : SANS_FONT
label game[:source], text_size: text_size
end
end
end

View File

@@ -5,89 +5,65 @@ class IMICFPS
include CommonMethods
def self.set_defaults
if $window.config.get(:options, :audio, :volume_master).nil?
$window.config[:options, :audio, :volume_master] = 1.0
if $window.config.get(:options, :audio, :volume_sound).nil?
$window.config[:options, :audio, :volume_sound] = 1.0
end
if $window.config.get(:options, :audio, :volume_sound_effects).nil?
$window.config[:options, :audio, :volume_sound_effects] = 1.0
end
if $window.config.get(:options, :audio, :volume_music).nil?
$window.config[:options, :audio, :volume_music] = 0.7
end
if $window.config.get(:options, :audio, :volume_dialogue).nil?
$window.config[:options, :audio, :volume_dialogue] = 0.7
end
end
def setup
@categories = %w[
Display
Graphics
Audio
Controls
Multiplayer
]
@pages = {}
@current_page = nil
label "Settings", text_size: 100, color: Gosu::Color::BLACK
flow(width: 1.0, height: 1.0) do
stack(width: 0.25, height: 1.0) do
end
stack(width: 0.5, height: 1.0) do
stack(width: 1.0, height: 0.25) do
title "Settings"
flow(width: 1.0) do
link I18n.t("menus.back"), width: nil do
pop_state
end
button get_image("#{GAME_ROOT_PATH}/static/icons/settings_display.png"), image_width: 64, tip: I18n.t("settings.display") do
show_page(:display)
end
button get_image("#{GAME_ROOT_PATH}/static/icons/settings_graphics.png"), image_width: 64, tip: I18n.t("settings.graphics") do
show_page(:graphics)
end
button get_image("#{GAME_ROOT_PATH}/static/icons/settings_audio.png"), image_width: 64, tip: I18n.t("settings.audio") do
show_page(:audio)
end
button get_image("#{GAME_ROOT_PATH}/static/icons/settings_controls.png"), image_width: 64, tip: I18n.t("settings.controls") do
show_page(:controls)
end
button get_image("#{GAME_ROOT_PATH}/static/icons/settings_multiplayer.png"), image_width: 64, tip: I18n.t("settings.multiplayer") do
show_page(:multiplayer)
end
@categories.each do |category|
button category, width: 1.0 do
show_page(:"#{category}".downcase)
end
end
@page_container = stack(width: 1.0, height: 0.75, scroll: true) do
button I18n.t("menus.back"), width: 1.0, margin_top: 64 do
pop_state
end
end
@categories.each do |category|
stack(width: 0.5, height: 1.0) do |element|
@pages[:"#{category}".downcase] = element
element.hide
send(:"create_page_#{category}".downcase) if respond_to?(:"create_page_#{category}".downcase)
end
end
end
# @categories.each do |category|
# stack(width: 0.5, height: 1.0) do |element|
# @pages[:"#{category}".downcase] = element
# element.hide
# send(:"create_page_#{category}".downcase) if respond_to?(:"create_page_#{category}".downcase)
# end
# end
# end
show_page(:display)
end
def show_page(page)
@page_container.clear do
send(:"page_#{page}")
if element = @pages[page]
@current_page&.hide
@current_page = element
element.show
end
@page_container.scroll_top = 0
end
def page_display
def create_page_display
label "Display", text_size: 50
label "Resolution"
@@ -102,7 +78,7 @@ class IMICFPS
end
end
check_box "Fullscreen", margin_top: 25, margin_bottom: 25
check_box "Fullscreen", padding_top: 25, padding_bottom: 25
stack do
longest_string = "Gamma Correction"
@@ -133,37 +109,29 @@ class IMICFPS
end
end
def page_audio
def create_page_audio
label "Audio", text_size: 50
longest_string = "Dialogue".length
volumes = %i[master sound_effects music dialogue]
volumes = %i[sound music dialogue]
stack(width: 1.0) do
stack do
volumes.each do |volume|
config_value = window.config.get(:options, :audio, :"volume_#{volume}")
flow(width: 1.0, margin_bottom: 10) do
flow(width: 0.25) do
label volume.to_s.split("_").map(&:capitalize).join(" ").ljust(longest_string, " ")
end
flow(width: 0.5) do
instance_variable_set(:"@volume_#{volume}", slider(range: 0.0..1.0, value: config_value, width: 1.0))
instance_variable_get(:"@volume_#{volume}").subscribe(:changed) do |_sender, value|
instance_variable_get(:"@volume_#{volume}_label").value = format("%03.2f%%", value * 100.0)
window.config[:options, :audio, :"volume_#{volume}"] = value
end
end
flow(width: 0.25) do
instance_variable_set(:"@volume_#{volume}_label", label(format("%03.2f%%", config_value * 100.0)))
flow do
label volume.to_s.split("_").join(" ").capitalize.ljust(longest_string, " ")
instance_variable_set(:"@volume_#{volume}", slider(range: 0.0..1.0, value: config_value))
instance_variable_get(:"@volume_#{volume}").subscribe(:changed) do |_sender, value|
instance_variable_get(:"@volume_#{volume}_label").value = format("%03.2f%%", value * 100.0)
window.config[:options, :audio, :"volume_#{volume}"] = value
end
instance_variable_set(:"@volume_#{volume}_label", label(format("%03.2f%%", config_value * 100.0)))
end
end
end
end
def page_controls
def create_page_controls
label "Controls", text_size: 50
InputMapper.keymap.each do |key, values|
@@ -182,7 +150,7 @@ class IMICFPS
end
end
def page_graphics
def create_page_graphics
label "Graphics", text_size: 50
longest_string = "Surface Effect Detail"
@@ -250,7 +218,7 @@ class IMICFPS
end
end
def page_multiplayer
def create_page_multiplayer
label "Multiplayer", text_size: 50
flow do

View File

@@ -1,21 +0,0 @@
# frozen_string_literal: true
class IMICFPS
class SinglePlayerMenu < Menu
def setup
title I18n.t("menus.singleplayer")
link "Tutorial", enabled: false, tip: "No tutorial implemented, yet..."
link "Campaign", enabled: false, tip: "No campaign, yet..."
link "Multiplayer Practice" do
push_state(LevelSelectMenu)
end
link I18n.t("menus.back"), margin_top: 25 do
pop_state
end
end
end
end

100
lib/ui/subcommand.rb Normal file
View File

@@ -0,0 +1,100 @@
# frozen_string_literal: true
class IMICFPS
class Commands
class Command
class SubCommand
def initialize(parent, command, type)
@parent = parent
@command = command
@type = type
end
attr_reader :command
def handle(arguments, console)
if arguments.size > 1
console.stdin("to many arguments for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.size)} expected #{Style.notice(1)}.")
return
end
case @type
when :boolean
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || false
console.stdin("#{command}: #{Style.highlight(var)}")
when "on"
var = @parent.set(command.to_sym, true)
console.stdin("#{command} => #{Style.highlight(var)}")
when "off"
var = @parent.set(command.to_sym, false)
console.stdin("#{command} => #{Style.highlight(var)}")
else
console.stdin("Invalid argument for #{Style.highlight(command.to_s)}, got #{Style.error(arguments.last)} expected #{Style.notice('on')}, or #{Style.notice('off')}.")
end
when :string
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "\"\""
console.stdin("#{command}: #{Style.highlight(var)}")
else
var = @parent.set(command.to_sym, arguments.last)
console.stdin("#{command} => #{Style.highlight(var)}")
end
when :integer
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "nil"
console.stdin("#{command}: #{Style.highlight(var)}")
else
begin
var = @parent.set(command.to_sym, Integer(arguments.last))
console.stdin("#{command} => #{Style.highlight(var)}")
rescue ArgumentError
console.stdin("Error: #{Style.error("Expected an integer, got '#{arguments.last}'")}")
end
end
when :decimal
case arguments.last
when "", nil
var = @parent.get(command.to_sym) || "nil"
console.stdin("#{command}: #{Style.highlight(var)}")
else
begin
var = @parent.set(command.to_sym, Float(arguments.last))
console.stdin("#{command} => #{Style.highlight(var)}")
rescue ArgumentError
console.stdin("Error: #{Style.error("Expected a decimal or integer, got '#{arguments.last}'")}")
end
end
else
raise RuntimeError
end
end
def values
case @type
when :boolean
%w[on off]
else
[]
end
end
def usage
case @type
when :boolean
"#{Style.highlight(command)} #{Style.notice('[on|off]')}"
when :string
"#{Style.highlight(command)} #{Style.notice('[string]')}"
when :integer
"#{Style.highlight(command)} #{Style.notice('[0]')}"
when :decimal
"#{Style.highlight(command)} #{Style.notice('[0.0]')}"
end
end
end
end
end
end

View File

@@ -2,10 +2,17 @@
class IMICFPS
class Window < CyberarmEngine::Window
attr_accessor :needs_cursor
attr_accessor :number_of_vertices, :needs_cursor
attr_reader :renderer, :scene, :config, :director, :console, :delta_time
def setup
def initialize(window_width = 1280, window_height = 720, fullscreen = false)
fps_target = ARGV.first.to_i != 0 ? ARGV.first.to_i : 60
if ARGV.join.include?("--native")
super(width: Gosu.screen_width, height: Gosu.screen_height, fullscreen: true, resizable: true, update_interval: 1000.0 / fps_target)
else
super(width: window_width, height: window_height, fullscreen: fullscreen, resizable: true, update_interval: 1000.0 / fps_target)
end
$window = self
I18n.load_path << Dir["#{GAME_ROOT_PATH}/locales/*.yml"]
I18n.default_locale = :en
language = Gosu.language.split("_").first.to_sym
@@ -22,8 +29,7 @@ class IMICFPS
@config = CyberarmEngine::ConfigFile.new(file: "#{IMICFPS::GAME_ROOT_PATH}/data/config.json")
@show_console = false
@console = Console.new
CyberarmEngine::Console::Command.setup
@console.stdin("=== #{IMICFPS::NAME} v#{IMICFPS::VERSION} (#{IMICFPS::RELEASE_NAME}) ===\n\n")
Commands::Command.setup
SettingsMenu.set_defaults
@renderer = Renderer.new
@@ -37,7 +43,7 @@ class IMICFPS
@config.save!
end
push_state(CyberarmEngine::IntroState, forward: Boot)
push_state(Boot)
@delta_time = Gosu.milliseconds
end
@@ -54,12 +60,6 @@ class IMICFPS
end
end
def input_hijack=(hijacker)
@input_hijacker = hijacker
InputMapper.reset_keys
end
def needs_cursor?
false
end
@@ -89,6 +89,7 @@ class IMICFPS
@overlay.update
SoundManager.update
@number_of_vertices = 0
@delta_time = Gosu.milliseconds
end
@@ -99,14 +100,12 @@ class IMICFPS
def button_down(id)
if @show_console
@console.button_down(id)
elsif @input_hijacker
@input_hijacker.button_down(id)
else
super
end
if id == Gosu::KbBacktick
@show_console ? @console.blur : @console.focus && InputMapper.reset_keys
@show_console ? @console.blur : @console.focus
@show_console = !@show_console
end
end
@@ -114,8 +113,6 @@ class IMICFPS
def button_up(id)
if @show_console
@console.button_up(id)
elsif @input_hijacker
@input_hijacker.button_up(id)
else
super
end

View File

@@ -10,10 +10,4 @@ en:
back: Back
leave: Leave
paused: Paused
resume: Resume
settings:
display: Display
graphics: Graphics
audio: Audio
controls: Controls
multiplayer: Multiplayer
resume: Resume

View File

@@ -1,30 +0,0 @@
{
"metadata": {
"name": "Model Viewer",
"gamemode": null,
"authors": [
"Cyberarm"
],
"datetime": "2021-04-25 22:51:00 UTC",
"thumbnail": "",
"description": "A map for the model viewer tool to use."
},
"entities": [],
"spawnpoints": [
{
"team": 0,
"position": {
"x": 0,
"y": 0,
"z": 0
},
"orientation": {
"x": 0,
"y": 0,
"z": 0
}
}
]
}

View File

@@ -6,7 +6,6 @@ layout (location = 2) out vec3 fragNormal;
layout (location = 3) out vec3 fragUV;
in vec3 outPosition, outColor, outNormal, outUV, outFragPos, outCameraPos;
out vec4 outputFragColor;
flat in int outHasTexture;
uniform sampler2D diffuse_texture;
@@ -24,7 +23,4 @@ void main() {
fragColor = vec4(result, 1.0);
fragNormal = outNormal;
fragUV = outUV;
float gamma = 2.2;
outputFragColor.rgb = pow(fragColor.rgb, vec3(1.0 / gamma));
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,114 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
viewBox="0 0 33.866666 33.866668"
version="1.1"
id="svg8"
sodipodi:docname="settings_audio.svg"
inkscape:export-filename="C:\Users\cyber\Code\i-mic-fps\static\icons\settings_audio.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6568542"
inkscape:cx="59.160271"
inkscape:cy="53.794164"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="8" />
<sodipodi:guide
position="2.1166666,16.933334"
orientation="1,0"
id="guide837" />
<sodipodi:guide
position="16.933333,31.750001"
orientation="0,-1"
id="guide839" />
<sodipodi:guide
position="31.749999,16.933334"
orientation="1,0"
id="guide841" />
<sodipodi:guide
position="19.05,2.1166667"
orientation="0,-1"
id="guide843" />
<sodipodi:guide
position="16.933333,27.516668"
orientation="1,0"
id="guide855" />
<sodipodi:guide
position="16.933333,16.933334"
orientation="0,-1"
id="guide857" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#deddda;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 16.933333,8.4666671 12.7,10.583333 H 6.3500001 4.2333333 L 2.1166666,12.7 l 1e-7,8.466667 2.1166668,2.116667 H 12.7 l 4.233333,2.116667 H 19.05 l 0,-16.9333339 z"
id="path863"
sodipodi:nodetypes="cccccccccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:1.05833334;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 21.166667,8.4666669 C 25.4,12.7 25.4,21.166667 21.166667,25.400001"
id="path865"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1.05833334;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283334,8.4666667 c 4.233333,4.2333333 4.233333,12.7000003 0,16.9333343"
id="path865-8"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1.05833334;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 25.4,8.4666667 C 29.633333,12.7 29.633333,21.166667 25.4,25.400001"
id="path865-6"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.52916667;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 16.933333,8.4666671 V 25.400001"
id="path896" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,127 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
viewBox="0 0 33.866666 33.866668"
version="1.1"
id="svg8"
sodipodi:docname="settings_controls.svg"
inkscape:export-filename="C:\Users\cyber\Code\i-mic-fps\static\icons\settings_controls.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6568542"
inkscape:cx="59.417753"
inkscape:cy="46.567825"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-intersection-paths="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="8" />
<sodipodi:guide
position="2.1166666,16.933334"
orientation="1,0"
id="guide837" />
<sodipodi:guide
position="16.933333,31.750001"
orientation="0,-1"
id="guide839" />
<sodipodi:guide
position="31.749999,16.933334"
orientation="1,0"
id="guide841" />
<sodipodi:guide
position="19.05,2.1166667"
orientation="0,-1"
id="guide843" />
<sodipodi:guide
position="16.933333,27.516668"
orientation="1,0"
id="guide855" />
<sodipodi:guide
position="16.933333,16.933334"
orientation="0,-1"
id="guide857" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#deddda;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="rect898"
width="19.050001"
height="12.700001"
x="2.1166668"
y="10.583334"
rx="1.058336"
ry="1.0583334" />
<ellipse
style="fill:#deddda;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path900"
cx="27.516666"
cy="17.197916"
rx="3.9245155"
ry="6.3499994" />
<rect
style="fill:#deddda;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="rect902"
width="12.964583"
height="1.5875001"
x="4.2333336"
y="20.902084"
rx="1.102295e-06"
ry="1.0583334" />
<path
style="fill:none;stroke:#000000;stroke-width:0.52916667;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 24.077084,14.816667 h 6.879167"
id="path904" />
<path
style="fill:none;stroke:#000000;stroke-width:0.52916667;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 27.516667,10.847917 v 3.96875"
id="path906" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
viewBox="0 0 33.866666 33.866668"
version="1.1"
id="svg8"
sodipodi:docname="settings_display.svg"
inkscape:export-filename="C:\Users\cyber\Code\i-mic-fps\static\icons\settings_display.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="38.887284"
inkscape:cy="53.219323"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="8" />
<sodipodi:guide
position="2.1166666,16.933334"
orientation="1,0"
id="guide837" />
<sodipodi:guide
position="16.933333,31.750001"
orientation="0,-1"
id="guide839" />
<sodipodi:guide
position="31.749999,16.933334"
orientation="1,0"
id="guide841" />
<sodipodi:guide
position="19.05,2.1166667"
orientation="0,-1"
id="guide843" />
<sodipodi:guide
position="16.933333,27.516668"
orientation="1,0"
id="guide855" />
<sodipodi:guide
position="16.933333,16.933334"
orientation="0,-1"
id="guide857" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#deddda;stroke:#000000;stroke-width:0.52916667;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 16.933334,23.283334 -4.233334,0 -6.3499999,6.35 H 27.516667 l -6.35,-6.35 z"
id="path853"
sodipodi:nodetypes="cccccc" />
<rect
style="fill:#c0bfbc;stroke-width:0.52916667;stroke:#000000;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round;stroke-linecap:round"
id="rect845"
width="29.633331"
height="16.933334"
x="2.1166668"
y="6.3499999"
ry="2.1166663"
rx="2.1166663" />
<rect
style="fill:#ffffff;stroke-width:1.05833"
id="rect849"
width="25.400002"
height="12.700001"
x="4.2333336"
y="8.4666672"
ry="1.4885254e-06"
rx="1.4885254e-06" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,103 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
viewBox="0 0 33.866666 33.866668"
version="1.1"
id="svg8"
sodipodi:docname="settings_graphics.svg"
inkscape:export-filename="C:\Users\cyber\Code\i-mic-fps\static\icons\settings_graphics.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="40.619584"
inkscape:cy="57.766917"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="8" />
<sodipodi:guide
position="2.1166666,16.933334"
orientation="1,0"
id="guide837" />
<sodipodi:guide
position="16.933333,31.750001"
orientation="0,-1"
id="guide839" />
<sodipodi:guide
position="31.749999,16.933334"
orientation="1,0"
id="guide841" />
<sodipodi:guide
position="19.05,2.1166667"
orientation="0,-1"
id="guide843" />
<sodipodi:guide
position="16.933333,27.516668"
orientation="1,0"
id="guide855" />
<sodipodi:guide
position="16.933333,16.933334"
orientation="0,-1"
id="guide857" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#f6f5f4;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 31.750001,10.583333 -8.466667,6.35 L 31.750001,25.4 Z"
id="path861" />
<rect
style="fill:#deddda;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="rect859"
width="23.283335"
height="14.816667"
x="2.1166666"
y="10.583333"
rx="1.058336"
ry="1.0583334" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB