Add files via upload

This commit is contained in:
bakustarver 2024-11-18 17:17:57 +02:00 committed by GitHub
parent c0883b78b8
commit 07dd85c6fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 11333 additions and 0 deletions

View file

@ -0,0 +1,129 @@
Kawariki MKXP runtime
=====================
The MKXP runtime handles games based on the `Ruby Game Scripting System`, RGSS.
The RPG Maker editions based on RGSS are:
- RPGMaker XP (RGSS1)
- RPGMaker VX (RGSS2)
- RPGMaker VX Ace (RGSS3)
[Official RPGMaker Website][rpgmakerweb]
MKXP-Z
------
The specific implementation used in Kawariki is [mkxp-z][mkxp-z] by Roza/Struma,
itself based on the original [mkxp][mkxp-github] by Ancurio.
Currently, a [repackaged distribution][mkxp-z-repack] of the [official releases][mkxp-z-releases] are used.
This is required mostly for ease of automatic downloading in Kawariki.
Links: [mkxp-z GitHub][mkxp-z-github]
Configuration
-------------
### mkxp.json
The mkxp-z configuration is merged from a number of files in order:
- `<kawariki>/mkxp/mkxp.json`: Global mkxp config
- `<game>/kawariki-mkxp.json`: Overrides specific for this game
See the [default included with mkxp-z for reference][mkxp-z-config]
Note that the runtime doesn't understand JSON comments for now,
unlike mkxp-z itself.
#### RTPs
The situation with RTPs is not optimal at the moment, but they
can be specified globally in `<kawariki>/mkxp/mkxp.json`:
```json
{
"RTP": ["/path/to/rtp"]
}
```
### Environment Variables
- `KAWARIKI_MKXP_DUMP_SCRIPTS=<dirname>`: Dump scripts to <dirname> before applying patches
- `KAWARIKI_MKXP_DUMP_PATCHED_SCRIPTS=<dirname>`: Dump scripts to <dirname> after applying patches
- `KAWARIKI_MKXP_FILTER_SCRIPTS=<name>[,...]`: Blacklist scripts/plugins by name
- `KAWARIKI_MKXP_DRY_RUN=1`: Exit after patching scripts, don't run game
- `KAWARIKI_MKXP_NO_FONT_EFFECTS=1`: Disable all font effects. See relevant patch in `pathces.rb`
- `KAWARIKI_NO_OVERLAYNS=1` Disallow usage of overlayns-static
### versions.json
This file specifies the mkxp-z versions known to the runtime:
- `variant` *required string* Must be `"mkxp-z"` for now
- `version` *required array-of-numbers* The version of this distribution (e.g. `[2,3,0]`)
- `dist` *required string* The name of the directory the distribution is stored in.
- `dist_url` *optional string* An URL to download the distribution from if not already available. It must point to a gzip/bzip2/xz compressed tar-archive.
- `name` *optional string* An optional name given to the distribution. Defaults to the value of `dist`.
RGSS Plugins
------------
All functionality in RGSS-based games is derived from the core scripts
of it's respective RPGMaker edition and extended by usually rather large
numbers of third-party engine plugins.
Unfortunately, as a consequence of RGSS being a rather simplistic engine
and also being Windows-only, a lot of these plugins rely on assumptions
that don't hold true on Linux (though MKXP already implements case-insensitive
path lookups) or depend on the Win32 API or other (possibly custom) native
Windows libraries by way of a FFI (`Win32API` in RGSS).
As such, the runtime must apply a considerable amount of patches to a game
to allow it to run on Linux/MXKP-Z.
### Ports
Ports are modifications or re-implementations of third-party plugins to make
them work on Linux/MKXP-Z. They are contained in the `ports/` directory.
All ports retain their original license terms. If you are the original author
of one of the included scripts and want it removed, please open an issue on GitHub.
### Patches
A patch tries to identify a third-party plugin and then either modify it's code
or outright replace the plugin with a port from `ports/`. Patches are defined
in [`patches.rb`](patches.rb)
### Win32API Stubs
Win32API stubs are implemented in [`libs/Win32API.rb`](libs/Win32API.rb).
They are automatically loaded if any reference to Win32API is found in plugin
code after all patches are applied.
It is usually easier to port a heavily Win32API-dependent plugin instead of
trying to re-implement the relevant Win32 APIs.
Currently, implementations are included for these common APIs:
- kernel32/GetPrivateProfileString
- kernel32/GetPrivateProfileInt
- kernel32/WritePrivateProfileString
### Preload
The preload script `preload.rb` is registered with MKXP-Z and is responsible for
applying the patches defined in `patches.rb`.
Some methods are provided for use in ports/patches:
- `Preload::require()`: Require a library from the `libs/` directory (see below)
- `Preload::print()`: Print to stderr. Note that Kernel.print may open a message box depending on RGSS version
### Libraries
The `libs/` directory contains a few libraries to support porting plugins.
- `ruby18.rb`: Some low-hanging compatibility modifications for RGSS1, which used Ruby 1.8
- `Win32API.rb`: Wrapper around Win32API to intercept imports with included ruby implementations
- `PreloadIni.rb`: Simple INI parser/generator for implementing {Get,Write}PrivateProfileString
- `XP_TileMapOverrideLib.rb`: Workaround for a MKXP-Z issue related to GL texture sizes
<!-- References -->
[mkxp-z]: https://roza-gb.gitbook.io/mkxp-z
[mkxp-z-config]: https://github.com/mkxp-z/mkxp-z/blob/release/mkxp.json
[mkxp-z-github]: https://github.com/mkxp-z/mkxp-z
[mkxp-z-releases]: https://github.com/mkxp-z/mkxp-z/releases
[mkxp-z-repack]: https://github.com/Orochimarufan/Kawariki/releases/tag/mkxp-2.3.0-kk
[mkxp-github]: https://github.com/Ancurio/mkxp
[rpgmakerweb]: https://www.rpgmakerweb.com/

View file

@ -0,0 +1,439 @@
# INI file tools for replacing Win32API usage
# Key and Section names are case-preserving
# Authors: Taeyeon Mori
module Preload
module Ini
# ********** Machinery **********
class DummyReadFile
def each_line(&p)
end
end
class IniBase
def initialize(file)
@file = file
@section = ""
@section_lc = ""
end
attr_reader :section, :section_lc
def self.open(filename)
if block_given? then
File.open(filename, self::FileMode) do |file|
yield self.new file
end
else
return self.new File.new(filename, self::FileMode)
end
end
end
class IniWriter < IniBase
FileMode = "wt"
def initialize(file)
super file
@newline = 1
end
def writeLine(line=nil)
if line.nil? || line.empty? then
@file.write "\r\n"
@newline += 1
else
@file.write "#{line}\r\n"
@newline = 0
end
end
def writeComment(text)
writeLine "; #{text}"
end
def writeSection(name)
lc = name.downcase
return if lc == @section_lc
writeLine if @newline < 1
writeLine "[#{name}]"
@section = name
@section_lc = lc
end
def writeKey(key, value)
value = value.to_s
value = "\"#{value}\"" if value.strip != value
writeLine "#{key}=#{value}"
end
def writeEntry(section, key, value)
writeSection section
writeKey key, value
end
def forward(token, *args)
# Can receive tokens from IniReader directly
case token
when :comment
writeComment *args
when :section
writeSection *args
when :key
writeKey *args
when :line
writeLine *args
when :empty
writeLine
when :eof
else
raise "Unknown token: #{token}"
end
end
end
class IniReader < IniBase
FileMode = "rt"
def self.open(filename)
# Pretend file is empty if it doesn't exist
if !File.exist? filename then
if block_given? then
yield self.new DummyReadFile.new
else
return self.new DummyReadFile.new
end
else
return super
end
end
def readComment(line)
line.slice!(0, line[1] == " " ? 2 : 1)
[line]
end
def readSection(line)
raise "Malformed section header: '#{line}'" if line[0] != '[' || line[-1] != ']'
@section = line[1...-1]
@section_lc = @section.downcase
return [@section]
end
def readKey(line)
key, value = line.split('=', 2)
value.strip!
# Allow quoting to keep surrounding whitespace
value = value[1...-1] if value.size > 1 && "'\"".include?(value[0]) && value[0] == value[-1]
[key.strip, value]
rescue ArgumentError => e
puts "Error processing line: #{line.inspect} - #{e.message}"
nil
end
def readLine(line)
if line.valid_encoding?
line.strip!
else
# Optionally, handle the invalid encoding here:
# You can choose to skip the line, replace invalid characters, or log it.
line = line.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
end
return [:empty] if line.empty?
return :comment, *readComment(line) if line[0] == ';'
return :section, *readSection(line) if line[0] == '['
return :key, *readKey(line) if line.include? '='
# Just pass it through as last resort
return :line, line
end
def read
raise "Must be called with block" unless block_given?
last = nil
@file.each_line do |line|
tok = readLine line
# Collapse empty lines
next if tok[0] == :empty && last == :empty
last = tok[0]
yield tok
end
yield :eof
end
end
# ********** Simple API **********
# Read
def self.readIniString(filename, section, key)
# Intended to be compatible with ReadPrivateProfileString
section_lc = section.downcase
key_lc = key.downcase
found_section = section.empty?
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
found_section = ir.section_lc == section_lc
when :key
fkey, fval = *args
return fval if found_section && fkey.downcase == key_lc
end
end
end
end
def self.readIniStrings(filename, section, keys=nil)
result = {}
section_lc = section.downcase
keys_lc = {}
keys.each {|key| keys_lc[key.downcase] = key} unless keys.nil?
found_section = section.empty?
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
found_section = ir.section_lc == section_lc
when :key
if found_section then
fkey, fval = *args
if keys.nil? then
result[fkey] = fval
else
zkey = keys_lc[fkey.downcase]
result[zkey] = fval unless zkey.nil?
end
end
end
end
end
return result
end
def self.readIniPaths(filename, paths=nil)
result = {}
paths_lc = {}
paths.each {|path| lc = path.downcase; paths_lc[lc] = path unless paths_lc.include? lc} unless paths.nil?
section_all = paths.nil? || paths.include?("/*")
section_all_name = nil
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
unless paths.nil? then
section_all_name = paths_lc["#{ir.section_lc}/*"]
section_all = !section_all_name.nil?
section_all_name.slice!(-2...) if section_all
end
when :key
fkey, fval = *args
if section_all then
fpath = "#{section_all_name.nil? ? ir.section : section_all_name}/#{fkey}"
result[fpath] = fval
else
fpath = "#{ir.section_lc}/#{fkey.downcase}"
result[paths_lc[fpath]] = fval if paths_lc.key? fpath
end
end
end
end
result
end
def self.readIniSections(filename)
# Get a list of sections in the file
# Note: may contain (possibly case-mismatched) duplicates.
sections = []
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
sections.push args[0]
end
end
end
return sections
end
def self.readIniKeys(filename, section)
# Get a list of keys in a section
# Note: may contain (possibly case-mismatched) duplicates.
keys = []
section_lc = section.downcase
found_section = section.empty?
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
found_section = ir.section_lc == section_lc
when :key
keys.push args[0] if found_section
end
end
end
return keys
end
# Write
def self.writeIniString(filename, section, key, value)
# Intended to be compatible with WritePrivateProfileString
# Write to new file then rename over top.
section_lc = section.downcase
key_lc = key.downcase unless key.nil?
found_section = section.empty?
written = key.nil? || value.nil? # Delete instead if nil
temp_name = "#{filename}.tmp"
IniWriter.open temp_name do |iw|
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
# Insert new key before leaving section
if found_section && !written then
iw.writeKey key, value
written = true
end
# Start new section or omit whole section if key == nil
found_section = ir.section_lc == section_lc
iw.writeSection ir.section unless found_section && key.nil?
when :key
fkey, fval = *args
if found_section then
if fkey.downcase == key_lc then
# Replace matching key
iw.writeKey key, value unless written
written = true
elsif !key.nil? then
# Copy other keys
iw.writeKey fkey, fval
end
else
iw.writeKey fkey, fval
end
when :eof
# Add to end of file if not found earlier
iw.writeEntry section, key, value unless written
else
iw.forward type, *args
end
end
end
end
File.rename(temp_name, filename)
end
def self.writeIniStrings(filename, section, hash)
# Write to new file then rename over top.
section_lc = section.downcase
hash_lc = {}
written = []
hash.each_pair do |key, value|
key_lc = key.downcase
hash_lc[key_lc] = [key, value] unless value.nil?
written.push key_lc if value.nil?
end
found_section = section.empty?
temp_name = "#{filename}.tmp"
IniWriter.open temp_name do |iw|
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
# Insert new keys before leaving section
if found_section && !hash_lc.empty? then
hash_lc.each_pair {|key_lc, kv| iw.writeKey *kv}
written.push *hash_lc.keys
hash_lc.clear
end
# Start new section or omit whole section if key == nil
found_section = ir.section_lc == section_lc
iw.writeSection ir.section
when :key
fkey, fval = *args
if found_section then
fkey_lc = fkey.downcase
entry = hash_lc.delete fkey_lc
if !entry.nil? then
# Replace matching key
iw.writeKey *entry
written.push fkey_lc
next
elsif written.include? fkey_lc then
next
end
end
iw.writeKey fkey, fval
when :eof
# Add to end of file if not found earlier
if !hash_lc.empty? then
iw.writeSection section
hash_lc.each_value {|key, value| iw.writeKey key, value}
end
else
iw.forward type, *args
end
end
end
end
File.rename(temp_name, filename)
end
def self.writeIniPaths(filename, hash)
# Write to new file then rename over top.
sections = {}
hash.each_pair do |path, value|
section, _, key = path.rpartition '/'
section_lc = section.downcase
sections[section_lc] = {name: section, written: [], keys: {}} unless sections.key? section_lc
sections[section_lc][:keys][key.downcase] = [key, value]
end
current_section = sections[""]
temp_name = "#{filename}.tmp"
IniWriter.open temp_name do |iw|
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
# Write new keys before leaving section
if !current_section.nil? then
current_section[:keys].each_value {|key, value| iw.writeKey key, value}
current_section[:written].push *current_section[:keys].keys
current_section[:keys].clear
end
# new section
iw.writeSection ir.section
current_section = sections[ir.section_lc]
when :key
fkey, fval = *args
fkey_lc = fkey.downcase
# Replace matching key
if !current_section.nil? then
replacement = current_section[:keys].delete fkey_lc
if !replacement.nil? then
iw.writeKey *replacement unless replacement[1].nil?
current_section[:written].push fkey_lc
next
elsif current_section[:written].include? fkey_lc then
next
end
end
# Copy other keys
iw.writeKey fkey, fval
when :eof
# Add sections not previously seen
sections.each_value do |sect|
if !sect[:keys].empty? then
iw.writeSection sect[:name]
sect[:keys].each_value do |key, value|
iw.writeKey key, value
end
end
end
else
iw.forward type, *args
end
end
end
end
File.rename(temp_name, filename)
end
end
end

View file

@ -0,0 +1,130 @@
=begin
Win32API emulation
MKXP-Z exposes an implementation of the Win32API module (called MiniFFI) by default.
However, this alias is only actually useful on Windows. Therefore, we replace it
with a pure-ruby version specifically implementing the most common imports.
Real native libraries can still be accessed through MiniFII.new (e.g. in ports)
Lambdas are used for implementations as they replicate Win32API's #call interface.
=end
# Don't expose MiniFFI as Win32API
Object.remove_const :Win32API
module Win32API
module Kernel32
GetPrivateProfileInt = GetPrivateProfileIntA = ->(appname, keyname, default, filename) do
Preload.require "PreloadIni.rb"
s = Preload::Ini.readIniString filename, appname, keyname
s.nil? ? default : s.to_i
end
GetPrivateProfileString = GetPrivateProfileStringA = ->(appname, keyname, default, ret, size, filename) do
Preload.require "PreloadIni.rb"
if appname.nil? then
res = Preload::Ini.readIniSections(filename).join("\0") + "\0"
elsif keyname.nil? then
res = Preload::Ini.readIniKeys(filename, appname).join("\0") + "\0"
else
s = Preload::Ini.readIniString filename, appname, keyname
res = s.nil? ? (default.nil? ? "" : default) : s
end
# C-String dance
size -= 1
if res.size > size then
res.slice!(size...)
res[size-1] = "\0" if appname.nil? or keyname.nil?
end
ret[...res.size] = res
ret[res.size] = "\0"
res.size
end
WritePrivateProfileString = WritePrivateProfileStringA = ->(appname, keyname, value, filename) do
Preload.require "PreloadIni.rb"
Preload::Ini.writeIniString filename, appname, keyname, value
end
end
module User32
FindWindow = FindWindowA = ->(cls, wnd) do
return 1
end
FindWindowEx = ->(parent, ca, cls, wnd) do
return 1
end
GetAsyncKeyState = ->(key) do
# Very naive
return 128 if Input.pressex? key
return 0
end
GetClientRect = ->(hWnd, out_rect) do
return 0 unless hWnd == 1
# out_rect.byteslice(0, 16, [0, 0, Graphics.width, Graphics.height].pack("llll"))
out_rect[0, 16] = [0, 0, Graphics.width, Graphics.height].pack("llll")
return 1
end
GetCursorPos = ->(out_point) do
# Pack mouse coordinates into a binary string
packed_coords = [Input.mouse_x, Input.mouse_y].pack("ll")
# Update the out_point string with the packed data (overwrite first 8 bytes)
out_point[0, 8] = packed_coords
return 1
end
GetKeyboardLayout = ->(thread) do
return 0
end
GetSystemMetrics = ->(index) do
return Graphics.width if index == 0 # SM_CXSCREEN - Primary screen width
return Graphics.height if index == 1 # SM_CYSCREEN - Primary screen height
return 0 if index == 4 # SM_CYCAPTION - Height of caption area (title bar?)
return 0 if index == 5 # SM_CXBORDER - Width of window borders
return 0 if index == 6 # SM_CYBORDER - Height of window borders
return 0 if index == 23 # SM_SWAPBUTTON - Swap left/right mouse buttons
return 0 if index == 45 # SM_CXEDGE - Width of 3D window borders
Preload.print("Warning: user32#GetSystemMetrics index #{index} not implemented")
return 0
end
GetWindowRect = ->(hWnd, out_rect) do
return 0 unless hWnd == 1
# out_rect.byteslice(0, 16, [0, 0, Graphics.width, Graphics.height].pack("llll"))
out_rect[0, 16] = [0, 0, Graphics.width, Graphics.height].pack("llll")
return 1
end
MapVirtualKeyEx = ->(code, map, layout) do
return 0 unless layout == 0
return code
end
ScreenToClient = ->(hWnd, point) do
return 1 unless hWnd != 1
return 0
end
ShowCursor = ->(show) do
Graphics.show_cursor = show == 1
return show
end
end
module SteamAPI
# TODO: Forward to native steamapi?
SteamAPI_Init = ->{1}
SteamAPI_Shutdown = ->{}
end
Libraries = {
"kernel32" => Kernel32,
"user32" => User32,
"steam_api" => SteamAPI,
}
def self.new(dllname, func, *rest)
dllname = dllname[...-4] if dllname[...-4] == ".dll"
lib = Libraries[dllname]
return lib.const_get(func, false) if lib.const_defined?(func, false) unless lib.nil?
Preload.print("Warning: Win32API not implemented: #{dllname}##{func}")
return ->(*args){Preload.print "(STUB) #{dllname}##{func}: #{args}"}
end
end

View file

@ -0,0 +1,346 @@
=begin
Win32API emulation
MKXP-Z exposes an implementation of the Win32API module (called MiniFFI) by default.
However, this alias is only actually useful on Windows. Therefore, we replace it
with a pure-ruby version specifically implementing the most common imports.
Real native libraries can still be accessed through MiniFII.new (e.g. in ports)
Lambdas are used for implementations as they replicate Win32API's #call interface.
=end
module Scancodes
SDL = { :UNKNOWN => 0x00,
:A => 0x04, :B => 0x05, :C => 0x06, :D => 0x07,
:E => 0x08, :F => 0x09, :G => 0x0A, :H => 0x0B,
:I => 0x0C, :J => 0x0D, :K => 0x0E, :L => 0x0F,
:M => 0x10, :N => 0x11, :O => 0x12, :P => 0x13,
:Q => 0x14, :R => 0x15, :S => 0x16, :T => 0x17,
:U => 0x18, :V => 0x19, :W => 0x1A, :X => 0x1B,
:Y => 0x1C, :Z => 0x1D, :N1 => 0x1E, :N2 => 0x1F,
:N3 => 0x20, :N4 => 0x21, :N5 => 0x22, :N6 => 0x23,
:N7 => 0x24, :N8 => 0x25, :N9 => 0x26, :N0 => 0x27,
:RETURN => 0x28, :ESCAPE => 0x29, :BACKSPACE => 0x2A, :TAB => 0x2B,
:SPACE => 0x2C, :MINUS => 0x2D, :EQUALS => 0x2E, :LEFTBRACKET => 0x2F,
:RIGHTBRACKET => 0x30, :BACKSLASH => 0x31, :NONUSHASH => 0x32, :SEMICOLON => 0x33,
:APOSTROPHE => 0x34, :GRAVE => 0x35, :COMMA => 0x36, :PERIOD => 0x37,
:SLASH => 0x38, :CAPSLOCK => 0x39, :F1 => 0x3A, :F2 => 0x3B,
:F3 => 0x3C, :F4 => 0x3D, :F5 => 0x3E, :F6 => 0x3F,
:F7 => 0x40, :F8 => 0x41, :F9 => 0x42, :F10 => 0x43,
:F11 => 0x44, :F12 => 0x45, :PRINTSCREEN => 0x46, :SCROLLLOCK => 0x47,
:PAUSE => 0x48, :INSERT => 0x49, :HOME => 0x4A, :PAGEUP => 0x4B,
:DELETE => 0x4C, :END => 0x4D, :PAGEDOWN => 0x4E, :RIGHT => 0x4F,
:LEFT => 0x50, :DOWN => 0x51, :UP => 0x52, :NUMLOCKCLEAR => 0x53,
:KP_DIVIDE => 0x54, :KP_MULTIPLY => 0x55, :KP_MINUS => 0x56, :KP_PLUS => 0x57,
:KP_ENTER => 0x58, :KP_1 => 0x59, :KP_2 => 0x5A, :KP_3 => 0x5B,
:KP_4 => 0x5C, :KP_5 => 0x5D, :KP_6 => 0x5E, :KP_7 => 0x5F,
:KP_8 => 0x60, :KP_9 => 0x61, :KP_0 => 0x62, :KP_PERIOD => 0x63,
:NONUSBACKSLASH => 0x64, :APPLICATION => 0x65, :POWER => 0x66, :KP_EQUALS => 0x67,
:F13 => 0x68, :F14 => 0x69, :F15 => 0x6A, :F16 => 0x6B,
:F17 => 0x6C, :F18 => 0x6D, :F19 => 0x6E, :F20 => 0x6F,
:F21 => 0x70, :F22 => 0x71, :F23 => 0x72, :F24 => 0x73,
:EXECUTE => 0x74, :HELP => 0x75, :MENU => 0x76, :SELECT => 0x77,
:STOP => 0x78, :AGAIN => 0x79, :UNDO => 0x7A, :CUT => 0x7B,
:COPY => 0x7C, :PASTE => 0x7D, :FIND => 0x7E, :MUTE => 0x7F,
:VOLUMEUP => 0x80, :VOLUMEDOWN => 0x81, :LOCKINGCAPSLOCK => 0x82, :LOCKINGNUMLOCK => 0x83,
:LOCKINGSCROLLLOCK => 0x84, :KP_COMMA => 0x85, :KP_EQUALSAS400 => 0x86, :INTERNATIONAL1 => 0x87,
:INTERNATIONAL2 => 0x88, :INTERNATIONAL3 => 0x89, :INTERNATIONAL4 => 0x8A, :INTERNATIONAL5 => 0x8B,
:INTERNATIONAL6 => 0x8C, :INTERNATIONAL7 => 0x8D, :INTERNATIONAL8 => 0x8E, :INTERNATIONAL9 => 0x8F,
:LANG1 => 0x90, :LANG2 => 0x91, :LANG3 => 0x92, :LANG4 => 0x93,
:LANG5 => 0x94, :LANG6 => 0x95, :LANG7 => 0x96, :LANG8 => 0x97,
:LANG9 => 0x98, :ALTERASE => 0x99, :SYSREQ => 0x9A, :CANCEL => 0x9B,
:CLEAR => 0x9C, :PRIOR => 0x9D, :RETURN2 => 0x9E, :SEPARATOR => 0x9F,
:OUT => 0xA0, :OPER => 0xA1, :CLEARAGAIN => 0xA2, :CRSEL => 0xA3,
:EXSEL => 0xA4, :KP_00 => 0xB0, :KP_000 => 0xB1, :THOUSANDSSEPARATOR => 0xB2,
:DECIMALSEPARATOR => 0xB3, :CURRENCYUNIT => 0xB4, :CURRENCYSUBUNIT => 0xB5, :KP_LEFTPAREN => 0xB6,
:KP_RIGHTPAREN => 0xB7, :KP_LEFTBRACE => 0xB8, :KP_RIGHTBRACE => 0xB9, :KP_TAB => 0xBA,
:KP_BACKSPACE => 0xBB, :KP_A => 0xBC, :KP_B => 0xBD, :KP_C => 0xBE,
:KP_D => 0xBF, :KP_E => 0xC0, :KP_F => 0xC1, :KP_XOR => 0xC2,
:KP_POWER => 0xC3, :KP_PERCENT => 0xC4, :KP_LESS => 0xC5, :KP_GREATER => 0xC6,
:KP_AMPERSAND => 0xC7, :KP_DBLAMPERSAND => 0xC8, :KP_VERTICALBAR => 0xC9, :KP_DBLVERTICALBAR => 0xCA,
:KP_COLON => 0xCB, :KP_HASH => 0xCC, :KP_SPACE => 0xCD, :KP_AT => 0xCE,
:KP_EXCLAM => 0xCF, :KP_MEMSTORE => 0xD0, :KP_MEMRECALL => 0xD1, :KP_MEMCLEAR => 0xD2,
:KP_MEMADD => 0xD3, :KP_MEMSUBTRACT => 0xD4, :KP_MEMMULTIPLY => 0xD5, :KP_MEMDIVIDE => 0xD6,
:KP_PLUSMINUS => 0xD7, :KP_CLEAR => 0xD8, :KP_CLEARENTRY => 0xD9, :KP_BINARY => 0xDA,
:KP_OCTAL => 0xDB, :KP_DECIMAL => 0xDC, :KP_HEXADECIMAL => 0xDD, :LCTRL => 0xE0,
:LSHIFT => 0xE1, :LALT => 0xE2, :LGUI => 0xE3, :RCTRL => 0xE4,
:RSHIFT => 0xE5, :RALT => 0xE6, :RGUI => 0xE7, :MODE => 0x101,
:AUDIONEXT => 0x102, :AUDIOPREV => 0x103, :AUDIOSTOP => 0x104, :AUDIOPLAY => 0x105,
:AUDIOMUTE => 0x106, :MEDIASELECT => 0x107, :WWW => 0x108, :MAIL => 0x109,
:CALCULATOR => 0x10A, :COMPUTER => 0x10B, :AC_SEARCH => 0x10C, :AC_HOME => 0x10D,
:AC_BACK => 0x10E, :AC_FORWARD => 0x10F, :AC_STOP => 0x110, :AC_REFRESH => 0x111,
:AC_BOOKMARKS => 0x112, :BRIGHTNESSDOWN => 0x113, :BRIGHTNESSUP => 0x114, :DISPLAYSWITCH => 0x115,
:KBDILLUMTOGGLE => 0x116, :KBDILLUMDOWN => 0x117, :KBDILLUMUP => 0x118, :EJECT => 0x119,
:SLEEP => 0x11A, :APP1 => 0x11B, :APP2 => 0x11C
}
SDL.default = SDL[:UNKNOWN]
WIN32 = {
:LBUTTON => 0x01, :RBUTTON => 0x02, :MBUTTON => 0x04,
:BACK => 0x08, :TAB => 0x09, :RETURN => 0x0D, :SHIFT => 0x10,
:CONTROL => 0x11, :MENU => 0x12, :PAUSE => 0x13, :CAPITAL => 0x14,
:ESCAPE => 0x1B, :SPACE => 0x20, :PRIOR => 0x21, :NEXT => 0x22,
:END => 0x23, :HOME => 0x24, :LEFT => 0x25, :UP => 0x26,
:RIGHT => 0x27, :DOWN => 0x28, :PRINT => 0x2A, :INSERT => 0x2D,
:DELETE => 0x2E,
:N0 => 0x30, :N1 => 0x31, :N2 => 0x32, :N3 => 0x33,
:N4 => 0x34, :N5 => 0x35, :N6 => 0x36, :N7 => 0x37, :N8 => 0x38,
:N9 => 0x39,
:A => 0x41, :B => 0x42, :C => 0x43, :D => 0x44, :E => 0x45, :F => 0x46,
:G => 0x47, :H => 0x48, :I => 0x49, :J => 0x4A, :K => 0x4B, :L => 0x4C,
:M => 0x4D, :N => 0x4E, :O => 0x4F, :P => 0x50, :Q => 0x51, :R => 0x52,
:S => 0x53, :T => 0x54, :U => 0x55, :V => 0x56, :W => 0x57, :X => 0x58,
:Y => 0x59, :Z => 0x5A,
:LWIN => 0x5B, :RWIN => 0x5C,
:NUMPAD0 => 0x60, :NUMPAD1 => 0x61, :NUMPAD2 => 0x62, :NUMPAD3 => 0x63,
:NUMPAD4 => 0x64, :NUMPAD5 => 0x65, :NUMPAD6 => 0x66, :NUMPAD7 => 0x67,
:NUMPAD8 => 0x68, :NUMPAD9 => 0x69,
:MULTIPLY => 0x6A, :ADD => 0x6B, :SEPARATOR => 0x6C, :SUBSTRACT => 0x6D,
:DECIMAL => 0x6E, :DIVIDE => 0x6F,
:F1 => 0x70, :F2 => 0x71, :F3 => 0x72, :F4 => 0x73,
:F5 => 0x74, :F6 => 0x75, :F7 => 0x76, :F8 => 0x77,
:F9 => 0x78, :F10 => 0x79, :F11 => 0x7A, :F12 => 0x7B,
:F13 => 0x7C, :F14 => 0x7D, :F15 => 0x7E, :F16 => 0x7F,
:F17 => 0x80, :F18 => 0x81, :F19 => 0x82, :F20 => 0x83,
:F21 => 0x84, :F22 => 0x85, :F23 => 0x86, :F24 => 0x87,
:NUMLOCK => 0x90, :SCROLL => 0x91,
:LSHIFT => 0xA0, :RSHIFT => 0xA1, :LCONTROL => 0xA2, :RCONTROL => 0xA3,
:LMENU => 0xA4, :RMENU => 0xA5, :OEM_1 => 0xBA,
:OEM_PLUS => 0xBB, :OEM_COMMA => 0xBC, :OEM_MINUS => 0xBD, :OEM_PERIOD => 0xBE,
:OEM_2 => 0xBF, :OEM_3 => 0xC0, :OEM_4 => 0xDB, :OEM_5 => 0xDC,
:OEM_6 => 0xDD, :OEM_7 => 0xDE
}
WIN32INV = WIN32.invert
WIN2SDL = {
:BACK => :BACKSPACE,
:CAPITAL => :CAPSLOCK,
:PRIOR => :PAGEUP, :NEXT => :PAGEDOWN,
:PRINT => :PRINTSCREEN,
:LWIN => :LGUI, :RWIN => :RGUI,
:NUMPAD0 => :KP_0, :NUMPAD1 => :KP_1, :NUMPAD2 => :KP_2, :NUMPAD3 => :KP_3,
:NUMPAD4 => :KP_4, :NUMPAD5 => :KP_5, :NUMPAD6 => :KP_6, :NUMPAD7 => :KP_7,
:NUMPAD8 => :KP_8, :NUMPAD9 => :KP_9,
:MULTIPLY => :KP_MULTIPLY, :ADD => :KP_PLUS, :SUBSTRACT => :KP_MINUS,
:DECIMAL => :KP_DECIMAL, :DIVIDE => :KP_DIVIDE,
:NUMLOCK => :NUMLOCKCLEAR, :SCROLL => :SCROLLLOCK,
:LCONTROL => :LCTRL, :RCONTROL => :RCTRL,
# FIXME: Fill these out
:LMENU => :LALT, :RMENU => :RALT, :OEM_1 => :SEMICOLON,
:OEM_PLUS => :UNKNOWN, :OEM_COMMA => :UNKNOWN, :OEM_MINUS => :UNKNOWN, :OEM_PERIOD => :UNKNOWN,
:OEM_2 => :UNKNOWN, :OEM_3 => :UNKNOWN, :OEM_4 => :UNKNOWN, :OEM_5 => :UNKNOWN,
:OEM_6 => :UNKNOWN, :OEM_7 => :UNKNOWN
}
WIN2SDL.default = :UNKNOWN
end
$win32KeyStates = nil
module Graphics
class << self
alias_method(:win32wrap_update, :update)
def update
win32wrap_update
$win32KeyStates = nil
end
end
end
def get_raw_keystates
if $win32KeyStates == nil
$win32KeyStates = Input.raw_key_states
end
return $win32KeyStates
end
def common_keystate(vkey)
vkey_name = Scancodes::WIN32INV[vkey]
states = get_raw_keystates
pressed = false
if vkey_name == :LBUTTON
pressed = Input.press?(Input::MOUSELEFT)
elsif vkey_name == :RBUTTON
pressed = Input.press?(Input::MOUSERIGHT)
elsif vkey_name == :MBUTTON
pressed = Input.press?(Input::MOUSEMIDDLE)
elsif vkey_name == :SHIFT
pressed = double_state(states, :LSHIFT, :RSHIFT)
elsif vkey_name == :MENU
pressed = double_state(states, :LALT, :RALT)
elsif vkey_name == :CONTROL
pressed = double_state(states, :LCTRL, :RCTRL)
else
scan = nil
if Scancodes::SDL.key?(vkey_name)
scan = vkey_name
else
scan = Scancodes::WIN2SDL[vkey_name]
end
pressed = state_pressed(states, scan)
end
return pressed ? 1 : 0
end
def memcpy_string(dst, src)
i = 0
src.each_byte do |b|
dst.setbyte(i, b)
i += 1
end
end
def state_pressed(states, sdl_scan)
return states[Scancodes::SDL[sdl_scan]]
end
def double_state(states, left, right)
return state_pressed(states, left) || state_pressed(states, right)
end
# Don't expose MiniFFI as Win32API
Object.remove_const :Win32API
module Win32API
module Kernel32
GetPrivateProfileInt = GetPrivateProfileIntA = ->(appname, keyname, default, filename) do
Preload.require "PreloadIni.rb"
s = Preload::Ini.readIniString filename, appname, keyname
s.nil? ? default : s.to_i
end
GetPrivateProfileString = GetPrivateProfileStringA = ->(appname, keyname, default, ret, size, filename) do
Preload.require "PreloadIni.rb"
if appname.nil? then
res = Preload::Ini.readIniSections(filename).join("\0") + "\0"
elsif keyname.nil? then
res = Preload::Ini.readIniKeys(filename, appname).join("\0") + "\0"
else
s = Preload::Ini.readIniString filename, appname, keyname
res = s.nil? ? (default.nil? ? "" : default) : s
end
# C-String dance
size -= 1
if res.size > size then
res.slice!(size...)
res[size-1] = "\0" if appname.nil? or keyname.nil?
end
ret[...res.size] = res
ret[res.size] = "\0"
res.size
end
WritePrivateProfileString = WritePrivateProfileStringA = ->(appname, keyname, value, filename) do
Preload.require "PreloadIni.rb"
Preload::Ini.writeIniString filename, appname, keyname, value
end
MultiByteToWideChar = MultiByteToWideCharA = ->(codepage, flags, input_str, input_len, buffer, buffer_size) do
puts codepage, flags, input_str, input_len, buffer, buffer_size
#Preload.require "PreloadIni.rb"
#Preload::Ini.writeIniString filename, appname, keyname, value
end
end
module User32
FindWindow = FindWindowA = ->(cls, wnd) do
return 1
end
FindWindowEx = ->(parent, ca, cls, wnd) do
return 1
end
GetAsyncKeyState = ->(key) do
# Very naive
return 128 if Input.pressex? key
return 0
end
GetClientRect = ->(hWnd, out_rect) do
return 0 unless hWnd == 1
# out_rect.byteslice(0, 16, [0, 0, Graphics.width, Graphics.height].pack("llll"))
out_rect[0, 16] = [0, 0, Graphics.width, Graphics.height].pack("llll")
return 1
end
GetCursorPos = ->(out_point) do
# Pack mouse coordinates into a binary string
packed_coords = [Input.mouse_x, Input.mouse_y].pack("ll")
# Update the out_point string with the packed data (overwrite first 8 bytes)
out_point[0, 8] = packed_coords
return 1
end
GetKeyState = ->(vkey) do
puts 'bbb'
return common_keystate(vkey[0])
end
GetKeyboardLayout = ->(thread) do
return 0
end
GetSystemMetrics = ->(index) do
return Graphics.width if index == 0 # SM_CXSCREEN - Primary screen width
return Graphics.height if index == 1 # SM_CYSCREEN - Primary screen height
return 0 if index == 4 # SM_CYCAPTION - Height of caption area (title bar?)
return 0 if index == 5 # SM_CXBORDER - Width of window borders
return 0 if index == 6 # SM_CYBORDER - Height of window borders
return 0 if index == 23 # SM_SWAPBUTTON - Swap left/right mouse buttons
return 0 if index == 45 # SM_CXEDGE - Width of 3D window borders
Preload.print("Warning: user32#GetSystemMetrics index #{index} not implemented")
return 0
end
GetWindowRect = ->(hWnd, out_rect) do
return 0 unless hWnd == 1
# out_rect.byteslice(0, 16, [0, 0, Graphics.width, Graphics.height].pack("llll"))
out_rect[0, 16] = [0, 0, Graphics.width, Graphics.height].pack("llll")
return 1
end
MapVirtualKeyEx = ->(code, map, layout) do
return 0 unless layout == 0
return code
end
ScreenToClient = ->(hWnd, point) do
return 1 unless hWnd != 1
return 0
end
ShowCursor = ->(show) do
Graphics.show_cursor = show == 1
return show
end
end
module SteamAPI
# TODO: Forward to native steamapi?
SteamAPI_Init = ->{1}
SteamAPI_Shutdown = ->{}
end
Libraries = {
"kernel32" => Kernel32,
"user32" => User32,
"steam_api" => SteamAPI,
}
def self.new(dllname, func, *rest)
dllname = dllname[...-4] if dllname[...-4] == ".dll"
lib = Libraries[dllname]
return lib.const_get(func, false) if lib.const_defined?(func, false) unless lib.nil?
Preload.print("Warning: Win32API not implemented: #{dllname}##{func}")
return ->(*args){Preload.print "(STUB) #{dllname}##{func}: #{args}"}
end
end

View file

@ -0,0 +1,136 @@
# ======================================================================
# MKXP-Z Custom tilemap workaround tools
#
# Authors: Roza, Taeyeon Mori
#
# Contains library code only, must be added to custom
# tilemap classes using a preload patch or similar.
# ======================================================================
# SUPER TILEMAP VERTICAL WRAPPER THING
#
# This is a little fix for Pokemon Essentials' custom tilemap code
# that works around MKXP's GPU texture size limit that would normally
# stop you from playing a lot of games.
#
# The concept is simple enough: If your tileset is too big, a new
# bitmap will be constructed with all the excess pixels sent to the
# image's right side. This basically means that you now have a limit
# far higher than you should ever actually need.
#
# 1024 -> 4096
# 2048 -> 16384 (enough to get the normal limit)
# 4096 -> 65536 (enough to load pretty much any tileset)
# 8192 -> 262144
# 16384 -> 1048576 (what most people have at this point)
#
# Because of the extra math the game will have to do to find the right
# pixels, this will probably cause a slight performance hit while on these
# maps which would normally be megasurfaces.
#
# This script was written for games based on 17.1. This workaround is
# already implemented in 19.
#
# ~Roza/Zoroark
#=======================================================================
module TileWrap
MAX_TEX_SIZE = Bitmap.max_size
TILESET_WIDTH = 0x100
MAX_TEX_SIZE_BOOSTED = MAX_TEX_SIZE**2/TILESET_WIDTH
def self.clamp(val, min, max)
val = max if val > max
val = min if val < min
return val
end
def self.wrapTileset(originalbmp)
width = originalbmp.width
height = originalbmp.height
if width == TILESET_WIDTH && originalbmp.mega?
columns = (height / MAX_TEX_SIZE.to_f).ceil
if columns * TILESET_WIDTH > MAX_TEX_SIZE
raise "Tilemap is too long!\n\nSIZE: #{originalbmp.height}px\nHARDWARE LIMIT: #{MAX_TEX_SIZE}px\nBOOSTED LIMIT: #{MAX_TEX_SIZE_BOOSTED}px"
end
bmp = Bitmap.new(TILESET_WIDTH*columns, MAX_TEX_SIZE)
remainder = height % MAX_TEX_SIZE
columns.times{|col|
srcrect = Rect.new(0, col * MAX_TEX_SIZE, width, (col + 1 == columns) ? remainder : MAX_TEX_SIZE)
bmp.blt(col*TILESET_WIDTH, 0, originalbmp, srcrect)
}
return bmp
end
return originalbmp
end
def self.wrapRect(srcrect)
column, y = srcrect.y.divmod MAX_TEX_SIZE
raise "Rect split across column wrap!" if y + srcrect.height > MAX_TEX_SIZE
return srcrect if column == 0
Rect.new(column * MAX_TEX_SIZE + srcrect.x, y, srcrect.width, srcrect.height)
end
def self.wrapRect!(rect)
column, y = rect.y.divmod MAX_TEX_SIZE
raise "Rect split across column wrap!" if y + rect.height > MAX_TEX_SIZE
return if column == 0
rect.x = column * MAX_TEX_SIZE + rect.x
rect.y = y
end
def self.blitWrappedPixels(destX, destY, dest, src, srcrect)
if (srcrect.y + srcrect.width < MAX_TEX_SIZE)
# Save the processing power
return dest.blt(destX, destY, src, srcrect)
end
merge = (srcrect.y % MAX_TEX_SIZE) > ((srcrect.y + srcrect.height) % MAX_TEX_SIZE)
srcrect.x = clamp(srcrect.x, 0,TILESET_WIDTH)
srcrect.width = clamp(srcrect.width, 0, TILESET_WIDTH - srcrect.x)
col = (srcrect.y / MAX_TEX_SIZE.to_f).floor
srcX = col * TILESET_WIDTH + srcrect.x
srcY = srcrect.y % MAX_TEX_SIZE
if !merge
dest.blt(destX, destY, src, Rect.new(srcX, srcY, srcrect.width, srcrect.height))
else
#FIXME won't work on heights longer than two columns, but nobody should need
# more than 32k pixels high at once anyway
side = {:a => MAX_TEX_SIZE - srcY, :b => srcrect.height - (MAX_TEX_SIZE - srcY)}
dest.blt(destX, destY, src, Rect.new(srcX, srcY, srcrect.width, side[:a]))
dest.blt(destX, destY + side[:a], src, Rect.new(srcX + TILESET_WIDTH, 0, srcrect.width, side[:b]))
end
end
# May be applied using Module#prepend
# it's probably better to integrate custom tilemap code manually
# XXX: is it OK to dispose of the original bitmaps?
module TilemapPatch
def tileset=(value)
if value.mega?
super TileWrap::wrapTileset value
value.dispose
else
super value
end
end
end
module SpritePatch
def bitmap=(bmp)
if bmp.mega?
super TileWrap.wrapTileset bmp
bmp.dispose
else
super bmp
end
end
def src_rect=(rect)
super TileWrap.wrapRect rect
end
end
end

View file

@ -0,0 +1,71 @@
# Ruby 1.8 compat
module Ruby18
module ObjectPatch
# Object#type used to be an alias to Object#class
def type
self.class
end
end
class IncludeStringArray < Array
def include?(thing)
if thing.is_a?(String) then
super(thing.to_sym)
else
super
end
end
end
module KernelPatch
# Used to return a string array
def methods(*)
IncludeStringArray.new super
end
def singleton_methods(*)
IncludeStringArray.new super
end
end
module ModulePatch
# Used to return string array.
# Fix instance_methods.include? use-case by patching it to work with strings
# The array will still be of symbols however
def instance_methods(*)
IncludeStringArray.new super
end
def public_instance_methods(*)
IncludeStringArray.new super
end
def private_instance_methods(*)
IncludeStringArray.new super
end
end
module ArrayPatch
def nitems
count {|i| !i.nil?}
end
def choice
sample
end
end
module HashPatch
def index(value)
key value
end
end
# Apply Patches
Object.prepend ObjectPatch
Module.prepend ModulePatch
Kernel.prepend KernelPatch
Array.prepend ArrayPatch
Hash.prepend HashPatch
end

View file

@ -0,0 +1,182 @@
# Kawariki MKXP patch/port collection
# See preload.rb for Patch implementation
module Preload
Patches = [
# Ports
Patch.new("Zeus Fullscreen: Use mkxp builtin fullscreen instead (Alt+Enter)")
.imported?(:Zeus_Fullscreen)
.replace!("Zeus_Fullscreen++.rb"),
Patch.new("Zeus_Map_Effects fix")
.imported?(:Zeus_Map_Effects)
.sub!(":zoom_blur_length, :motion_blur_rate", ":zoom_blur_length, :motion_blur_rate\ndef update_animation_Integer(str,str2,str3,str4,str5,str6) @wave_amp = 0.0; end"),
Patch.new("Zeus_Lights_Shadows fix")
.imported?(:Zeus_Lights_Shadows)
.sub!(/^RPG_VERSION =.*/, "RPG_VERSION ="+ ENV["vcode"])
.sub!(" def update_animation_value", "def update_animation_Integer(str,str2,str3,str4,str5) @wave_amp = 0.0; end\ndef update_animation_value"),
Patch.new("cyanic-SteamUserStatsLite fix")
.include?('Win32API.new(self.steam_dll_name')
# .remove!,
.replace!("achievements.rb"),
Patch.new("XIV's Simple Reputation System (SRS) for RGSS3 fix") #XIV's Simple Reputation System (SRS) for RGSS3 Romance Level Version: 1.1
.include?("Simple Reputation System (SRS)")
.sub!("$scene1 = Scene_Menu.new(0)", "$scene1 = Scene_Menu.new"),
Patch.new("Nergal's Item Finding patch fix") #Nergal's Item Finding
.include?("SEARCH_TYPES = [ ITEMS_TO_FIND_PERSON, ITEMS_TO_FIND_SCHOOL,")
.include?("$scene1 = Scene_Menu.new(0)")
.sub!("$scene1 = Scene_Menu.new(0)", "$scene1 = Scene_Menu.new"),
Patch.new("Nergal's XP Gain") #Nergal's XP Gain
.include?("CustomGab.display_message(stat + XpLevel::MAX_LVL_MESSAGE)")
.include?("$scene4 = Scene_Menu.new(0)")
.sub!("$scene4 = Scene_Menu.new(0)", "$scene4 = Scene_Menu.new"),
Patch.new("WF-RGSS base patch") #【WF-RGSS】共通rev29 base
.include?("'MultiByteToWideChar', %w(i l p i p i)")
# .remove!,
.replace!("testencode.rb"), #Window_Base
Patch.new("window check content height fix (mgq paradox)")
.if? {|script| script.name == "Window_Base"}
# .replace!("basewt.rb"),
.sub!("if contents_width > 0 && contents_height > 0", " if contents_height > 10000\nself.contents = Bitmap.new(1, 1)\nelsif contents_width > 0 && contents_height > 0"),
Patch.new("temp bitmap load crash fix monster girl paradox disable preview") #ベース/Module
#maybe the memory buffer is filled up too much??
# .if? {|script| script.name == "Window_Base"}
.include?('thumnail_file = "Save/Save#{Regexp.last_match(1)}.png"')
.sub!("@thumbnails[Regexp.last_match(1).to_i - 1] = Bitmap.new(thumnail_file)", "@thumbnails[Regexp.last_match(1).to_i - 1] = @dummy_thumbnail #Bitmap.new(thumnail_file)"),
# .replace!("savebitmanwin32api.rb"),
Patch.new("WF-RGSS Exit-EX patch test") #▼ メイン【WF-RGSS】Exit-EX 終了処理
.include?("Win32API.new('System/WFExit','hookExit','v','l')")
# .remove!,
.replace!("wxexittest.rb"),
Patch.new("HimeWorks' Event Trigger Labels: Fix any_key_pressed? implementation")
.imported?(:TH_EventTriggerLabels)
.replace!("TH_EventTriggerLabels.rb"),
Patch.new("HimeWorks' Simple Audio Encryption: Re-Implement with direct path detection")
.imported?(:TH_SimpleAudioEncryption)
.replace!("TH_SimpleAudioEncryption.rb"),
Patch.new("MOG_Anti_Lag: Fix visible type error")
.imported?(:mog_anti_lag)
.sub!("self.visible = @character.can_update", "self.visible = !!@character.can_update"),
Patch.new("KGC_BitmapExtension ? XP/VX ? error")
.imported?(:BitmapExtension)
# .sub!("class Win32API", "module Win32API"),
.remove!,
Patch.new("Galv's Event Pop-Ups: Fix bitmap constructor call")
.imported?("Galv_Map_Popups")
.match?(/\.font\.shadow\s*=\s*\d+/)
.sub!(/\.font\.shadow\s*=\s*\d+/, ".font.shadow = true"),
Patch.new("Basic Mouse Plugin test disable for now")
.imported?(nil)
.if? {|script| script.name == "BasicMouse"}
.include?("include IBasicMouse")
.sub!("def VZ; return @curr.lZ / 120; end", "def VZ; return 0 if @curr.nil? || @curr.lZ.nil?; return @curr.lZ / 120.0 end"),
Patch.new("Screenshot plugin")
.imported?(nil)
.include?('SAVE_NAME = "ScreenShot/%Y%m%d%H%M%S.png"')
# .sub!("def VZ; return @curr.lZ / 120; end", "def VZ; return 0 if @curr.nil? || @curr.lZ.nil?; return @curr.lZ / 120.0 end"),
# .replace!("BasicMouse.rb"),
.remove!,
Patch.new("Super simple mouse script: Use mkxp mouse API")
.imported?(nil)
.include?("SUPER SIMPLE MOUSE SCRIPT")
.replace!("Mouse.rb"),
Patch.new("RMXP CustomResolution plugin")
.imported?(nil)
.include?("def snapshot(filename = 'Data/snap', quality = 0)")
.replace!("XP_CustomResolution.rb"),
Patch.new("Glitchfinder's Key Input: Shim with MKXP builtins")
.imported?(nil)
.include?("unless method_defined?(:keyinputmodule_input_update)")
.replace!("Glitchfinder_Keyboard_Stub.rb"),
Patch.new("Auto Font Install: Already included in MKXP")
.imported?(nil)
.include?("# ** Auto Font Install")
.remove!,
Patch.new("Extended Music Script: MKXP already supports .mod. other formats aren't available.")
.imported?(nil)
.include?("# Extended Music Script Version 3.5")
.then!{|script|
return if script.context.flag? :incompatible_bgm_checked
unsupp = "wma,psf,minipsf,psf2,minipsf2,gsf,minigsf,usf,miniusf,hps,dsp,spc,gym,cym".split(",")
# TODO: Find unsupported files in Audio/BGM. Then show msgbox if no converted versions available
# Official MKXP-Z also doesn't support mp3
script.context.mark :incompatible_bgm_checked
}
.remove!,
Patch.new("CRDJ Input script: Use MKXP-Z input extensions")
.imported?(nil)
.include?("# A module that handles input data from a gamepad or keyboard.\r\n# Managed by symbols rather than button numbers in RGSS3.")
.replace!("CRDJ_Input.rb"),
# Specific Inline Patches
Patch.new("Shokushu de sennou patch")
.imported?(nil)
.include?("alias _cao_log_terminate_message terminate_message")
#.replace!("oldregex1.rb"),
.sub!("@text.gsub!(/[", "#"),
Patch.new("Try to fix superclass mismatches from MP Scene_Base")
.imported?(nil)
.include?("======\nMoonpearl's Scene_Base\n-----")
.flag!(:redefinitions_overwrite_class),
# Generic Inline Patches
Patch.new("Disable all font effects")
.flag?(:no_font_effects) # KAWARIKI_MKXP_NO_FONT_EFFECTS
.match?(/(\.f|F)ont\.(default_)?(italic|outline|shadow|bold)/)
# Font is a built-in API, so it's already available in preload
.then!{Font.default_italic = Font.default_outline = Font.default_shadow = Font.default_bold = false}
.sub!(/Font\.default_(italic|outline|shadow|bold)\s*=/, "Font.default_\\1 = false &&")
.sub!(/\.font\.(italic|outline|shadow|bold)\s*\=/, ".font.\\1 = false &&"),
Patch.new("Improve Ruby 1.8 Compatibility")
.if?{|script| script.context[:rgss_version] < 3} # Never on VX Ace, which shipped 1.9
.match?("self.type", /\Wtype\.to_s\W/, /\Winstance_methods\.include\?\(['"%]/)
.then!{require "ruby18.rb"},
Patch.new("Game_Player fix")
.imported?(nil)
.if? {|script| script.name == "Game_Player"}
.if? {|script| script.source.include? "else return true"}
.sub!("else return true", "true"),
Patch.new("KGC Bitmap Extension fix")
.imported?(nil)
.if? {|script| script.source.include? "@@reel_stop = RPG::SE.new(CAO::PSLOT::SOUND_REEL_STOP"}
.sub!("@@reel_stop =", "@reel_stop ="),
Patch.new("Response improvement remove")
.imported?(nil)
.match?(/#.*_wdtk_resinp_update/)
.remove!,
Patch.new("Response improvement patch")
.imported?(nil)
# .if? {|script| script.name == "Response improvement script"}
#.include?('@@press_count.each do')
.if? {|script| script.source.include? "_wdtk_resinp_update" }
.sub!('@@press_count', "@press_count")
.sub!(/\b_wdtk_resinp_update\b(?!\s*update)/, "_wdtk_resinp_update;\n@press_count ||= {}"),
# .sub!(/^(?!#.*)\b_wdtk_resinp_update\b(?!\s*update)/, "_wdtk_resinp_update;\n@press_count ||= {}"),
# .sub!(/^(?!#.*)\b_wdtk_resinp_update\b(?!\s*update)/) { "_wdtk_resinp_update;\n@press_count ||= {}" },
Patch.new("Item Script")
.imported?(nil)
# .if? {|script| script.name == "Response improvement script"}
#.include?('@@press_count.each do')
.include?("CATEGORIZE ITEM SCENE v1.0 ")
# .if? {|script| script.source.include? "_wdtk_resinp_update"}
.sub!("CATEGORY_IDENTIFIER.index(ITEM_DEFAULT_CATEGORY)", "CATEGORY_IDENTIFIER.keys.index(ITEM_DEFAULT_CATEGORY)")
.sub!("CATEGORY_IDENTIFIER.index(:", "CATEGORY_IDENTIFIER.key(:"),
Patch.new("Vitaminpl fix")
.imported?(nil)
.if? {|script| script.name == "Police"}
.if? {|script| script.source.include? "Lucida Sans Unicode"}
.if? {|script| script.source = "Font.default_size = 16"},
Patch.new("Vitaminpl 2 fix")
.imported?(nil)
.include?("module Wora_NSS")
.sub!("SCREENSHOT_IMAGE = true", "SCREENSHOT_IMAGE = false")
.sub!("PREMADE_IMAGE = true", "PREMADE_IMAGE = false"),
Patch.new("Dark Hero Party")
.imported?(nil)
.include?('text.push(self.to_s.scan(/#<(\S+):/)[0][0].to_s)')
.remove!,
Patch.new("Flirt quest")
.imported?(nil)
.include?('class Spriteset_BattleUnit')
.include?('Spriteset_Kiseki')
.sub!(/\bsuper\b(?!\s*\()/, 'super()'),
]
end

View file

@ -0,0 +1,158 @@
module Input
class BasicMouse
include IBasicMouse
# コンストラクタ
def initialize()
# 関連付けられたウィンドウのハンドル
@hWnd = 0
# 前回のマウスの状態
@prev = nil
# 現在のマウスの状態
@curr = nil
# ボタンの配列
@buttons = Array.new(self.NumButtons()){ |i| i = ButtonInfo.new() }
# 何かボタンが押されているか
@isPressedAnyButton = false
# ステータス情報文字列を更新すべきか
@needToUpdate = true
end
# 初期化処理
def Initialize(hWnd)
@hWnd = hWnd
@curr = DIMOUSESTATE2.new(hWnd)
end
# 更新処理
def Update
# マウスの更新処理
@prev = @curr
@curr.Update()
@isPressedAnyButton = false
for i in 0..NumButtons() - 1
@buttons[i].Update((@curr.rgbButtons[i] & 0x80) == 128 ? ButtonStatus::Pressed : ButtonStatus::Released)
if @buttons[i].Pressed()
@isPressedAnyButton = true;
end
end
@needToUpdate = true
end
# IBasicMouse::GetStatusString() オーバーライド
def GetStatusString
if @needToUpdate
def Static
leftButton = Keys::Key.new(0, "LeftButton", "左ボタン")
rightButton = Keys::Key.new(1, "RightButton", "右ボタン")
middleButton = Keys::Key.new(2, "MiddleButton", "中央ボタン")
xButton1 = Keys::Key.new(3, "XButton1", "Xボタン1")
xButton2 = Keys::Key.new(4, "XButton2", "Xボタン2")
xButton3 = Keys::Key.new(5, "XButton3", "Xボタン3")
xButton4 = Keys::Key.new(6, "XButton4", "Xボタン4")
xButton5 = Keys::Key.new(7, "XButton5", "Xボタン5")
@keyTable =
[
leftButton,
rightButton,
middleButton,
xButton1,
xButton2,
xButton3,
xButton4,
xButton5
]
end
self.Static()
puts sprintf("Position : (%5d,%5d)", self.X(), self.Y())
puts sprintf("Velocity : (%5d,%5d,%5d)", self.VX(), self.VY(), self.VZ())
puts "[ButtonName] [Pressed] [Released] [Repeated]"
for i in 0..(NumButtons() - 1)
puts sprintf(
"%-20s %4s%4s%6d %4s%4s %4s%6d",
@keyTable[i].GetName(),
@buttons[i].Pressed() ? "ON" : "OFF",
@buttons[i].JustPressed() ? "ON" : "OFF",
@buttons[i].GetContinuousCount(),
@buttons[i].Released() ? "ON" : "OFF",
@buttons[i].JustReleased() ? "ON" : "OFF",
@buttons[i].Repeated() ? "ON" : "OFF",
@buttons[i].GetRepeatCount()
)
end
print("\n")
end
end
# マウスの最大ボタン数
def NumButtons; return 8; end
# IBasicMouse::GetPosition() オーバーライド
def GetPosition
pos = System::Math::Vector2.Zero()
pos.x = @curr.lX; pos.y = @curr.lY;
return pos;
end
# IBasicMouse::SetPosition() オーバーライド
def SetPosition(x, y); Win32RGSS::Cursor::SetCursorPos(x, y); end
# IBasicMouse::GetVelocity() オーバーライド
def GetVelocity
pos = System::Math::Vector2.Zero()
pos.x = @curr.lX; pos.y = @curr.lY;
return pos
end
# IBasicMouse::X() オーバーライド
def X; return @curr.lX; end
# IBasicMouse::Y() オーバーライド
def Y; return @curr.lY; end
# IBasicMouse::VX() オーバーライド
def VX; return @curr.lX - @prev.lX; end
# IBasicMouse::VY() オーバーライド
def VY; return @curr.lY - @prev.lY; end
# IBasicMouse::VZ() オーバーライド
# def VZ; return @curr.lZ / 120; end
def VZ
# Check if @curr and @curr.lZ are valid (not nil)
return 0 if @curr.nil? || @curr.lZ.nil?
return @curr.lZ / 120.0 # Ensure division results in a float
end
# IBasicMouse::LeftButton() オーバーライド
def LeftButton; return @buttons[0]; end
# IBasicMouse::RightButton() オーバーライド
def RightButton; return @buttons[1]; end
# IBasicMouse::MiddleButton() オーバーライド
def MiddleButton; return @buttons[2]; end
# IBasicMouse::XButton1() オーバーライド
def XButton1; return @buttons[3]; end
# IBasicMouse::XButton2() オーバーライド
def XButton2; return @buttons[4]; end
# IBasicMouse::GetButtonState() オーバーライド
def GetButtonState(index); return @buttons[index]; end
# 1つ以上のボタンが押し下げられている時にはtrueを返します。
def IsPressedAnyButton; return @isPressedAnyButton; end
end
end

View file

@ -0,0 +1,452 @@
# CRDJ_Input.rb Kawariki MKXP-Z port
# Date: 2023-06-01
# TODO: - handle modified configuration
# - Properly deal with the layout stuff?
#==============================================================================
# ** Input
#-------------------------------------------------------------------------------
# Created By Cidiomar R. Dias Junior
# Originally posted at
#
# Terms of Use: Credit "Cidiomar R. Dias Junior"
#
# Maintained on Hime Works
#-------------------------------------------------------------------------------
# ** Description
#
# A module that handles input data from a gamepad or keyboard.
# Managed by symbols rather than button numbers in RGSS3.
#-------------------------------------------------------------------------------
# ** Usage
#
# Scroll down to the configuration section. It is around line 185.
# You can set up your key mappings there. Use the reference to get the names
# of the keys.
#
#===============================================================================
module CRDJ_Input
#--------------------------------------------------------------------------
# * Keymap in symbols
# To get a key from the keymap, you can use Input.key(sym) or Input:KEYMAP[sym]
# Or if you want to use a symbol in a input function, just pass the symbol
# as argument.
#--------------------------------------------------------------------------
KEYMAP = {
LBUTTON: 0x01, RBUTTON: 0x02,
CANCEL: 0x03, MBUTTON: 0x04,
XBUTTON1: 0x05, XBUTTON2: 0x06,
BACK: 0x08, TAB: 0x09,
CLEAR: 0x0c, RETURN: 0x0d,
SHIFT: 0x10, CONTROL: 0x11,
MENU: 0x12, PAUSE: 0x13,
CAPITAL: 0x14, KANA: 0x15,
JUNJA: 0x17, FINAL: 0x18,
HANJA: 0x19,
ESCAPE: 0x1b, CONVERT: 0x1c,
NONCONVERT: 0x1d, ACCEPT: 0x1e,
MODECHANGE: 0x1f, SPACE: 0x20,
PRIOR: 0x21, NEXT: 0x22,
END: 0x23, HOME: 0x24,
LEFT: 0x25, UP: 0x26,
RIGHT: 0x27, DOWN: 0x28,
SELECT: 0x29, PRINT: 0x2a,
EXECUTE: 0x2b, SNAPSHOT: 0x2c,
INSERT: 0x2d, DELETE: 0x2e,
HELP: 0x2f, N0: 0x30,
KEY_1: 0x31, KEY_2: 0x32,
KEY_3: 0x33, KEY_4: 0x34,
KEY_5: 0x35, KEY_6: 0x36,
KEY_7: 0x37, KEY_8: 0x38,
KEY_9: 0x39, colon: 0x3a,
semicolon: 0x3b, less: 0x3c,
equal: 0x3d, greater: 0x3e,
question: 0x3f, at: 0x40,
LETTER_A: 0x41, LETTER_B: 0x42,
LETTER_C: 0x43, LETTER_D: 0x44,
LETTER_E: 0x45, LETTER_F: 0x46,
LETTER_G: 0x47, LETTER_H: 0x48,
LETTER_I: 0x49, LETTER_J: 0x4a,
LETTER_K: 0x4b, LETTER_L: 0x4c,
LETTER_M: 0x4d, LETTER_N: 0x4e,
LETTER_O: 0x4f, LETTER_P: 0x50,
LETTER_Q: 0x51, LETTER_R: 0x52,
LETTER_S: 0x53, LETTER_T: 0x54,
LETTER_U: 0x55, LETTER_V: 0x56,
LETTER_W: 0x57, LETTER_X: 0x58,
LETTER_Y: 0x59, LETTER_Z: 0x5a,
LWIN: 0x5b, RWIN: 0x5c,
APPS: 0x5d, asciicircum: 0x5e,
SLEEP: 0x5f, NUMPAD0: 0x60,
NUMPAD1: 0x61, NUMPAD2: 0x62,
NUMPAD3: 0x63, NUMPAD4: 0x64,
NUMPAD5: 0x65, NUMPAD6: 0x66,
NUMPAD7: 0x67, NUMPAD8: 0x68,
NUMPAD9: 0x69, MULTIPLY: 0x6a,
ADD: 0x6b, SEPARATOR: 0x6c,
SUBTRACT: 0x6d, DECIMAL: 0x6e,
DIVIDE: 0x6f, F1: 0x70,
F2: 0x71, F3: 0x72,
F4: 0x73, F5: 0x74,
F6: 0x75, F7: 0x76,
F8: 0x77, F9: 0x78,
F10: 0x79, F11: 0x7a,
F12: 0x7b, F13: 0x7c,
F14: 0x7d, F15: 0x7e,
F16: 0x7f, F17: 0x80,
F18: 0x81, F19: 0x82,
F20: 0x83, F21: 0x84,
F22: 0x85, F23: 0x86,
F24: 0x87, NUMLOCK: 0x90,
SCROLL: 0x91, LSHIFT: 0xa0,
RSHIFT: 0xa1, LCONTROL: 0xa2,
RCONTROL: 0xa3, LMENU: 0xa4,
RMENU: 0xa5, BROWSER_BACK: 0xa6,
BROWSER_FORWARD: 0xa7, BROWSER_REFRESH: 0xa8,
BROWSER_STOP: 0xa9, BROWSER_SEARCH: 0xaa,
BROWSER_FAVORITES: 0xab, BROWSER_HOME: 0xac,
VOLUME_MUTE: 0xad, VOLUME_DOWN: 0xae,
VOLUME_UP: 0xaf, MEDIA_NEXT_TRACK: 0xb0,
MEDIA_PREV_TRACK: 0xb1, MEDIA_STOP: 0xb2,
MEDIA_PLAY_PAUSE: 0xb3, LAUNCH_MAIL: 0xb4,
LAUNCH_MEDIA_SELECT: 0xb5, LAUNCH_APP1: 0xb6,
LAUNCH_APP2: 0xb7, cedilla: 0xb8,
onesuperior: 0xb9, masculine: 0xba,
guillemotright: 0xbb, onequarter: 0xbc,
onehalf: 0xbd, threequarters: 0xbe,
questiondown: 0xbf, Agrave: 0xc0,
Aacute: 0xc1, Acircumflex: 0xc2,
Atilde: 0xc3, Adiaeresis: 0xc4,
Aring: 0xc5, AE: 0xc6,
Ccedilla: 0xc7, Egrave: 0xc8,
Eacute: 0xc9, Ecircumflex: 0xca,
Ediaeresis: 0xcb, Igrave: 0xcc,
Iacute: 0xcd, Icircumflex: 0xce,
Idiaeresis: 0xcf, ETH: 0xd0,
Ntilde: 0xd1, Ograve: 0xd2,
Oacute: 0xd3, Ocircumflex: 0xd4,
Otilde: 0xd5, Odiaeresis: 0xd6,
multiply: 0xd7, Oslash: 0xd8,
Ugrave: 0xd9, Uacute: 0xda,
Ucircumflex: 0xdb, Udiaeresis: 0xdc,
Yacute: 0xdd, THORN: 0xde,
ssharp: 0xdf, agrave: 0xe0,
aacute: 0xe1, acircumflex: 0xe2,
atilde: 0xe3, adiaeresis: 0xe4,
PROCESSKEY: 0xe5, ae: 0xe6,
PACKET: 0xe7, egrave: 0xe8,
eacute: 0xe9, ecircumflex: 0xea,
ediaeresis: 0xeb, igrave: 0xec,
iacute: 0xed, icircumflex: 0xee,
idiaeresis: 0xef, eth: 0xf0,
ntilde: 0xf1, ograve: 0xf2,
oacute: 0xf3, ocircumflex: 0xf4,
otilde: 0xf5, ATTN: 0xf6,
CRSEL: 0xf7, EXSEL: 0xf8,
EREOF: 0xf9, PLAY: 0xfa,
ZOOM: 0xfb, NONAME: 0xfc,
PA1: 0xfd, thorn: 0xfe,
ydiaeresis: 0xff
}
KEYMAP[:WIN] = [KEYMAP[:LWIN], KEYMAP[:RWIN]]
#===============================================================================
# Configuration
#===============================================================================
#--------------------------------------------------------------------------
# * Default Keys, you can configure here instead of by pressing F1.
#--------------------------------------------------------------------------
UP = [KEYMAP[:UP]]
DOWN = [KEYMAP[:DOWN]]
LEFT = [KEYMAP[:LEFT]]
RIGHT = [KEYMAP[:RIGHT]]
A = [KEYMAP[:SHIFT]]
B = [KEYMAP[:ESCAPE], KEYMAP[:LETTER_X]]
C = [KEYMAP[:RETURN], KEYMAP[:LETTER_Z]]
X = []
Y = []
Z = []
L = [KEYMAP[:PRIOR]]
R = [KEYMAP[:NEXT]]
F5 = [KEYMAP[:F5]]
F6 = [KEYMAP[:F6]]
F7 = [KEYMAP[:F7]]
F8 = [KEYMAP[:F8]]
F9 = [KEYMAP[:F9]]
SHIFT = [KEYMAP[:SHIFT]]
CTRL = [KEYMAP[:CONTROL]]
ALT = [KEYMAP[:MENU]]
#===============================================================================
# Rest of script
#===============================================================================
#--------------------------------------------------------------------------
# * Symbol version of default keys.
#--------------------------------------------------------------------------
SYM_KEYS = {
:UP => UP,
:LEFT => LEFT,
:DOWN => DOWN,
:RIGHT => RIGHT,
:A => A,
:B => B,
:C => C,
:X => X,
:Y => Y,
:Z => Z,
:L => L,
:R => R,
:F5 => F5,
:F6 => F6,
:F7 => F7,
:F8 => F8,
:F9 => F9,
:SHIFT => SHIFT,
:CTRL => CTRL,
:ALT => ALT
}
# Letters that can have accent
PssbLetters = "AEIOUCNYaeioucny"
# Accents, in ASCII, configured at runtime to avoid encoding troubles
Accents = [96.chr, 180.chr, 94.chr, 126.chr, 168.chr].join
NonCompatChars = [180, 168]
module InputExtension
#--------------------------------------------------------------------------
# * Determines whether the button corresponding to the symbol sym is
# currently being pressed.
#
# If the button is being pressed, returns TRUE. If not, returns FALSE.
#
# if Input.press?(:C)
# do_something
# end
#--------------------------------------------------------------------------
def press?(keys)
if keys.is_a?(Numeric)
k = keys.to_i
return (self.pressex? k)
elsif keys.is_a?(Array)
return keys.any? {|key| self.pressex?(key) }
elsif keys.is_a?(Symbol)
if SYM_KEYS.key?(keys)
return super(keys)
return SYM_KEYS[keys].any? {|key| self.pressex?(key) }
elsif KEYMAP.key?(keys)
k = KEYMAP[keys]
return self.pressex?(k)
else
return false
end
end
end
#--------------------------------------------------------------------------
# * Determines whether the button corresponding to the symbol sym is
# currently being pressed again.
# "Pressed again" is seen as time having passed between the button being
# not pressed and being pressed.
#
# If the button is being pressed, returns TRUE. If not, returns FALSE.
#--------------------------------------------------------------------------
def trigger?(keys)
if keys.is_a?(Numeric)
return Input.triggerex?(keys.to_i)
elsif keys.is_a?(Array)
return keys.any? {|key| self.triggerex?(key)}
elsif keys.is_a?(Symbol)
if SYM_KEYS.key?(keys)
return super(keys)
Preload.print "Trigger #{keys} #{SYM_KEYS[keys]} #{x}"
return SYM_KEYS[keys].any? {|key| self.triggerex?(key)}
elsif KEYMAP.key?(keys)
return self.triggerex?(KEYMAP[keys])
else
return false
end
end
end
#--------------------------------------------------------------------------
# * Determines whether the button corresponding to the symbol sym is
# currently being pressed again.
# Unlike trigger?, takes into account the repeated input of a button being
# held down continuously.
#
# If the button is being pressed, returns TRUE. If not, returns FALSE.
#--------------------------------------------------------------------------
def repeat?(keys)
if keys.is_a?(Numeric)
key = keys.to_i
return self.repeatex?(key)
elsif keys.is_a?(Array)
return keys.any? {|key| self.repeatex?(key)}
elsif keys.is_a?(Symbol)
if SYM_KEYS.key?(keys)
return super(keys)
return SYM_KEYS[keys].any? {|key| self.repeatex?(key)}
elsif KEYMAP.key?(keys)
return self.repeatex?(KEYMAP[keys])
else
return false
end
end
end
#--------------------------------------------------------------------------
# * Determines whether the button corresponding to the symbol sym
# was released.
#
# If the button was released, returns TRUE. If not, returns FALSE.
#--------------------------------------------------------------------------
def release?(keys)
if keys.is_a?(Numeric)
return self.releaseex? keys.to_i
elsif keys.is_a?(Array)
return keys.any? {|key| self.release?(key)}
elsif keys.is_a?(Symbol)
if SYM_KEYS.key?(keys)
return super(keys)
return SYM_KEYS[keys].any? {|key| self.releaseex?(key)}
elsif KEYMAP.key?(keys)
return self.releaseex?(KEYMAP[keys])
else
return false
end
end
end
#--------------------------------------------------------------------------
# * Checks the status of the directional buttons, translates the data into
# a specialized 4-direction input format, and returns the number pad
# equivalent (2, 4, 6, 8).
#
# If no directional buttons are being pressed (or the equivalent), returns 0.
#--------------------------------------------------------------------------
# def dir4
# return 2 if self.press?(DOWN)
# return 4 if self.press?(LEFT)
# return 6 if self.press?(RIGHT)
# return 8 if self.press?(UP)
# return 0
# end
#--------------------------------------------------------------------------
# * Checks the status of the directional buttons, translates the data into
# a specialized 8-direction input format, and returns the number pad
# equivalent (1, 2, 3, 4, 6, 7, 8, 9).
#
#If no directional buttons are being pressed (or the equivalent), returns 0.
#--------------------------------------------------------------------------
# def dir8
# down = self.press?(DOWN)
# left = self.press?(LEFT)
# return 1 if down and left
# right = self.press?(RIGHT)
# return 3 if down and right
# up = self.press?(UP)
# return 7 if up and left
# return 9 if up and right
# return 2 if down
# return 4 if left
# return 6 if right
# return 8 if up
# return 0
# end
#--------------------------------------------------------------------------
# * Gets the character that correspond to vk using the keyboard layout
#--------------------------------------------------------------------------
def get_character(vk)
# FIXME: do something smarter
return vk
end
#--------------------------------------------------------------------------
# * Accents Table, to bo used in conversion from ASCII to UTF8
#--------------------------------------------------------------------------
AccentsCharsConv = {
"A" => ["A", "A", "A", "A", "A"],
"E" => ["E", "E", "E", 0, "E"],
"I" => ["I", "I", "I", 0, "I"],
"O" => ["O", "O", "O", "O", "O"],
"U" => ["U", "U", "U", 0, "U"],
"C" => [ 0 , "C", 0 , 0, 0 ],
"N" => [ 0 , 0, 0 , "N", 0 ],
"Y" => [ 0 , "Y", 0 , 0, "?"],
"a" => ["a", "a", "a", "a", "a"],
"e" => ["e", "e", "e", 0 , "e"],
"i" => ["i", "i", "i", 0 , "i"],
"o" => ["o", "o", "o", "o", "o"],
"u" => ["u", "u", "u", 0 , "u"],
"c" => [ 0 , "c", 0 , 0 , 0 ],
"n" => [ 0 , 0 , 0 , "n", 0 ],
"y" => [ 0 , "y", 0 , 0 , "y"],
}
@last_accent = nil
#--------------------------------------------------------------------------
# * Get inputed string transcoded to UTF8
#--------------------------------------------------------------------------
def UTF8String
result = ""
31.upto(255) {|key|
if self.repeat?(key)
c = self.get_character(key)
if (cc = c.unpack("C")[0]) and NonCompatChars.include?(cc)
result += cc.chr
else
result += UNICODE_TO_UTF8.convertc if c != ""
end
end
}
return "" if result == ""
if @last_accent
result = @last_accent + result
@last_accent = nil
end
f_result = ""
jump = false
for i in 0 ... result.length
c = result[i]
if jump
jump = false
next
end
if Accents.include?c
if (nc = result[i+1]) != nil
if PssbLetters.include?(nc)
if (ac = AccentsCharsConv[nc][Accents.indexc]) != 0
f_result << ac
jump = true
else
f_result << c
f_result << nc
jump = true
end
elsif Accents.include?(nc)
f_result << c
f_result << nc
jump = true
else
f_result << c
f_result << nc
jump = true
end
else
@last_accent = c
end
else
f_result << c
end
end
return f_result
end
end
Input.singleton_class.prepend InputExtension
Input.const_set :KEYMAP, KEYMAP
end

View file

@ -0,0 +1,254 @@
# Glitchfinder's Key Input MKXP stub
# Only press? trigger? release? repeat? implemented
# Direct forwarding to respective MKXP Input.*ex? methods
# Key constants copied from 1.30 source
# ANYKEY is not implemented by MKXP
#
# Authors: Glitchfinder, Taeyeon Mori
# Date: 2022-01-31
module Keys
# ****************************** Key names ********************************
#--------------------------------------------------------------------------
# * Miscellaneous Keys
#--------------------------------------------------------------------------
CANCEL = 0x03 # Control-Break Processing
BACKSPACE = 0x08 # Backspace Key
TAB = 0x09 # Tab Key
CLEAR = 0x0C # Clear Key
RETURN = 0x0D # Enter Key
SHIFT = 0x10 # Shift Key
CONTROL = 0x11 # Ctrl Key
MENU = 0x12 # Alt Key
PAUSE = 0x13 # Pause Key
ESCAPE = 0x1B # Esc Key
CONVERT = 0x1C # IME Convert Key
NONCONVERT = 0x1D # IME Nonconvert Key
ACCEPT = 0x1E # IME Accept Key
SPACE = 0x20 # Space Bar Key (Space, usually blank)
PRIOR = 0x21 # Page Up Key
NEXT = 0x22 # Page Down Key
ENDS = 0x23 # End Key
HOME = 0x24 # Home Key
LEFT = 0x25 # Left Arrow Key
UP = 0x26 # Up Arrow Key
RIGHT = 0x27 # Right Arrow Key
DOWN = 0x28 # Down Arrow Key
SELECT = 0x29 # Select Key
PRINT = 0x2A # Print Key
EXECUTE = 0x2B # Execute Key
SNAPSHOT = 0x2C # Print Screen Key
DELETE = 0x2E # Delete Key
HELP = 0x2F # Help Key
LSHIFT = 0xA0 # Left Shift Key
RSHIFT = 0xA1 # Right Shift Key
LCONTROL = 0xA2 # Left Control Key (Ctrl)
RCONTROL = 0xA3 # Right Control Key (Ctrl)
LMENU = 0xA4 # Left Menu Key (Alt)
RMENU = 0xA5 # Right Menu Key (Alt)
PACKET = 0xE7 # Used to Pass Unicode Characters as Keystrokes
#--------------------------------------------------------------------------
# * Number Keys
#--------------------------------------------------------------------------
N0 = 0x30 # 0 Key
N1 = 0x31 # 1 Key
N2 = 0x32 # 2 Key
N3 = 0x33 # 3 Key
N4 = 0x34 # 4 Key
N5 = 0x35 # 5 Key
N6 = 0x36 # 6 Key
N7 = 0x37 # 7 Key
N8 = 0x38 # 8 Key
N9 = 0x39 # 9 Key
#--------------------------------------------------------------------------
# * Letter Keys
#--------------------------------------------------------------------------
A = 0x41 # A Key
B = 0x42 # B Key
C = 0x43 # C Key
D = 0x44 # D Key
E = 0x45 # E Key
F = 0x46 # F Key
G = 0x47 # G Key
H = 0x48 # H Key
I = 0x49 # I Key
J = 0x4A # J Key
K = 0x4B # K Key
L = 0x4C # L Key
M = 0x4D # M Key
N = 0x4E # N Key
O = 0x4F # O Key
P = 0x50 # P Key
Q = 0x51 # Q Key
R = 0x52 # R Key
S = 0x53 # S Key
T = 0x54 # T Key
U = 0x55 # U Key
V = 0x56 # V Key
W = 0x57 # W Key
X = 0x58 # X Key
Y = 0x59 # Y Key
Z = 0x5A # Z Key
#--------------------------------------------------------------------------
# * Windows Keys
#--------------------------------------------------------------------------
LWIN = 0x5B # Left Windows Key (Natural keyboard)
RWIN = 0x5C # Right Windows Key (Natural Keyboard)
APPS = 0x5D # Applications Key (Natural keyboard)
SLEEP = 0x5F # Computer Sleep Key
BROWSER_BACK = 0xA6 # Browser Back Key
BROWSER_FORWARD = 0xA7 # Browser Forward Key
BROWSER_REFRESH = 0xA8 # Browser Refresh Key
BROWSER_STOP = 0xA9 # Browser Stop Key
BROWSER_SEARCH = 0xAA # Browser Search Key
BROWSER_FAVORITES = 0xAB # Browser Favorites Key
BROWSER_HOME = 0xAC # Browser Start and Home Key
VOLUME_MUTE = 0xAD # Volume Mute Key
VOLUME_DOWN = 0xAE # Volume Down Key
VOLUME_UP = 0xAF # Volume Up Key
MEDIA_NEXT_TRACK = 0xB0 # Next Track Key
MEDIA_PREV_TRACK = 0xB1 # Previous Track Key
MEDIA_STOP = 0xB2 # Stop Media Key
MEDIA_PLAY_PAUSE = 0xB3 # Play/Pause Media Key
LAUNCH_MAIL = 0xB4 # Start Mail Key
LAUNCH_MEDIA_SELECT = 0xB5 # Select Media Key
LAUNCH_APP1 = 0xB6 # Start Application 1 Key
LAUNCH_APP2 = 0xB7 # Start Application 2 Key
PROCESSKEY = 0xE5 # IME Process Key
ATTN = 0xF6 # Attn Key
CRSEL = 0xF7 # CrSel Key
EXSEL = 0xF8 # ExSel Key
EREOF = 0xF9 # Erase EOF Key
PLAY = 0xFA # Play Key
ZOOM = 0xFB # Zoom Key
PA1 = 0xFD # PA1 Key
#--------------------------------------------------------------------------
# * Number Pad Keys
#--------------------------------------------------------------------------
NUMPAD0 = 0x60 # Numeric Keypad 0 Key
NUMPAD1 = 0x61 # Numeric Keypad 1 Key
NUMPAD2 = 0x62 # Numeric Keypad 2 Key
NUMPAD3 = 0x63 # Numeric Keypad 3 Key
NUMPAD4 = 0x64 # Numeric Keypad 4 Key
NUMPAD5 = 0x65 # Numeric Keypad 5 Key
NUMPAD6 = 0x66 # Numeric Keypad 6 Key
NUMPAD7 = 0x67 # Numeric Keypad 7 Key
NUMPAD8 = 0x68 # Numeric Keypad 8 Key
NUMPAD9 = 0x69 # Numeric Keypad 9 Key
MULTIPLY = 0x6A # Multiply Key (*)
ADD = 0x6B # Add Key (+)
SEPARATOR = 0x6C # Separator Key
SUBTRACT = 0x6D # Subtract Key (-)
DECIMAL = 0x6E # Decimal Key (.)
DIVIDE = 0x6F # Divide Key (/)
#--------------------------------------------------------------------------
# * Function Keys
#--------------------------------------------------------------------------
F1 = 0x70 # F1 Key
F2 = 0x71 # F2 Key
F3 = 0x72 # F3 Key
F4 = 0x73 # F4 Key
F5 = 0x74 # F5 Key
F6 = 0x75 # F6 Key
F7 = 0x76 # F7 Key
F8 = 0x77 # F8 Key
F9 = 0x78 # F9 Key
F10 = 0x79 # F10 Key
F11 = 0x7A # F11 Key
F12 = 0x7B # F12 Key
F13 = 0x7C # F13 Key
F14 = 0x7D # F14 Key
F15 = 0x7E # F15 Key
F16 = 0x7F # F16 Key
F17 = 0x80 # F17 Key
F18 = 0x81 # F18 Key
F19 = 0x82 # F19 Key
F20 = 0x83 # F20 Key
F21 = 0x84 # F21 Key
F22 = 0x85 # F22 Key
F23 = 0x86 # F23 Key
F24 = 0x87 # F24 Key
#--------------------------------------------------------------------------
# * Toggle Keys
#--------------------------------------------------------------------------
CAPITAL = 0x14 # Caps Lock Key
KANA = 0x15 # IME Kana Mode Key
HANGUL = 0x15 # IME Hangul Mode Key
JUNJA = 0x17 # IME Junja Mode Key
FINAL = 0x18 # IME Final Mode Key
HANJA = 0x19 # IME Hanja Mode Key
KANJI = 0x19 # IME Kanji Mode Key
MODECHANGE = 0x1F # IME Mode Change Request Key
INSERT = 0x2D # Insert Key
NUMLOCK = 0x90 # Num Lock Key
SCROLL = 0x91 # Scroll Lock Key
#--------------------------------------------------------------------------
# * OEM Keys (Vary by keyboard)
#--------------------------------------------------------------------------
OEM_1 = 0xBA # Misc Characters (; : in USA 101/102 Keyboards)
OEM_SEMICOLON = 0xBA
OEM_PLUS = 0xBB # + = Key
OEM_COMMA = 0xBC # , < Key
OEM_MINUS = 0xBD # - _ Key
OEM_PERIOD = 0xBE # . > Key
OEM_2 = 0xBF # Misc Characters (/ ? in USA 101/102 Keyboards)
OEM_SLASH = 0xBF
OEM_3 = 0xC0 # Misc Characters (` ~ in USA 101/102 Keyboards)
OEM_GRAVE = 0xC0
OEM_4 = 0xDB # Misc Characters ([ { in USA 101/102 Keyboards)
OEM_OPENBRACKET = 0xDB
OEM_5 = 0xDC # Misc Characters (\ | in USA 101/102 Keyboards)
OEM_BACKSLASH = 0xDC
OEM_6 = 0xDD # Misc Characters (] } in USA 101/102 Keyboards)
OEM_CLOSEBRACKET = 0xDD
OEM_7 = 0xDE # Misc Characters (' " in USA 101/102 Keyboards)
OEM_APOSTROPHE = 0xDE
OEM_8 = 0xDF # Misc Characters (Varies by Keyboard)
OEM_9 = 0xE1 # OEM Specific
OEM_10 = 0x92 # OEM Specific
OEM_11 = 0x93 # OEM Specific
OEM_12 = 0x94 # OEM Specific
OEM_13 = 0x95 # OEM Specific
OEM_14 = 0x96 # OEM Specific
OEM_15 = 0xE3 # OEM Specific
OEM_16 = 0xE4 # OEM Specific
OEM_17 = 0xE6 # OEM Specific
OEM_18 = 0xE9 # OEM Specific
OEM_19 = 0xEA # OEM Specific
OEM_20 = 0xEB # OEM Specific
OEM_21 = 0xEC # OEM Specific
OEM_22 = 0xED # OEM Specific
OEM_23 = 0xEE # OEM Specific
OEM_24 = 0xEF # OEM Specific
OEM_25 = 0xF1 # OEM Specific
OEM_26 = 0xF2 # OEM Specific
OEM_27 = 0xF3 # OEM Specific
OEM_28 = 0xF4 # OEM Specific
OEM_29 = 0xF5 # OEM Specific
OEM_102 = 0xE2 # Angle Bracket or Backslash on RT-102 Keyboards
OEM_CLEAR = 0xFE # Clear Key
#--------------------------------------------------------------------------
# * Special Keys
#--------------------------------------------------------------------------
#ANYKEY = 0x100 # Any Key
# ********************************* Stub **********************************
def self.update
end
def self.press?(key)
return Input.pressex? key
end
def self.trigger?(key)
return Input.triggerex? key
end
def self.repeat?(key)
return Input.repeatex? key
end
def self.release?(key)
return Input.releaseex? key
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,315 @@
=begin
#==============================================================================
Title: Event Trigger Labels
Author: Hime
Date: Dec 22, 2014
------------------------------------------------------------------------------
** Change log
Sep 09, 2022
- Add mkxp-z 2.4 support
Feb 13, 2022
- MKXP(-Z) port by Taeyeon Mori
- Replace any_key_pressed? implementation based on Win32API with
one using System.raw_key_states
Dec 22, 2014
- fixed bug where page list is not properly reset after execution
- added a flag for post-event processing for whether an event was triggered
Nov 6, 2014
- completely refactored code
- implemented "any key pressed?" check
- moved key item triggers and touch triggers into separate scripts
Jan 30, 2014
-refactored code
-added compatibility with instance items
Dec 27, 2013
-optimized performance by not clearing out the key item variable on update
Nov 19, 2013
-fixed bug where stepping on event with no active page crashed the game
Sep 26, 2013
-added support for "player touch" trigger label
Sep 6, 2013
-fixed bug where trigger labels as the first command doesn't register properly
Mar 22, 2013
-fixed bug where you can still trigger events after they have been erased
Dec 5, 2012
-fixed issue where event was checked before page was setup
Oct 20, 2012
-fixed issue where using key item in succession crashes game
-added support for key item triggers
Aug 22, 2012
-Support input names greater than one character (eg: F5 vs C)
Aug 18, 2012
-fixed label parsing to store all buttons
Jun 13, 2012
-initial release
------------------------------------------------------------------------------
** Terms of Use
* Free to use in non-commercial projects
* Contact me for commercial use
* No real support. The script is provided as-is
* Will do bug fixes, but no compatibility patches
* Features may be requested but no guarantees, especially if it is non-trivial
* Credits to Hime Works in your project
* Preserve this header
------------------------------------------------------------------------------
** Description:
This script allows you to assign multiple action triggers to an event.
Every page can have its own set of action triggers.
The action button, by default, is the C button (on keyboards, it is by default
the Z key, Enter, or Space). So when you press the action button when you're
standing beside an event, you will execute its action trigger and the event
will begin running.
Multiple action triggers allow you to set up your event to run different
sets of commands depending on which button you pressed. For example, you can
press the C button to interact with the event normally, or you can press the
X button (default A key) to initiate a mini-game.
------------------------------------------------------------------------------
** Installation
Place this script below Materials and above Main
------------------------------------------------------------------------------
** Usage
Instead of treating your page as one list of commands, you should instead
treat it as different sections of commands. Each section will have its own
label, specified in a specific format.
To create a section, add a Label command and then write
button?(:C)
This means that any commands under this label will be executed when you press
the C button (remember that the C button is the Z key on your keyboard).
You can create as many sections as you want, each with their own buttons.
Press F1 in-game and then go to the keyboard tab to see which buttons are
available.
#==============================================================================
=end
$imported = {} if $imported.nil?
$imported[:TH_EventTriggerLabels] = true
#==============================================================================
# ** Configuration
#==============================================================================
module TH
module Event_Trigger_Labels
# this is what you need to type for all your labels if you want to use
# the input branching
Button_Format = "button?"
#==============================================================================
# ** Rest of the script
#==============================================================================
Button_Regex = /#{Regexp.escape(Button_Format)}\(\:(.*)\)/
end
end
module Input
class << self
alias :th_any_key_pressed_check_update :update
end
# Not every key should be checked
Keys_To_Check = 4.upto(99) # SDL scancodes, most standard keys, no modifiers
if Input.respond_to?(:raw_key_states) then # MKXP-Z 2.4 +
def self.update
th_any_key_pressed_check_update
state = Input.raw_key_states
@any_key_pressed = Keys_To_Check.any?{|key| state[key]}
end
else
def self.update
th_any_key_pressed_check_update
state = System.raw_key_states
@any_key_pressed = Keys_To_Check.any?{|key| state[key] != 0}
end
end
def self.any_key_pressed?
@any_key_pressed
end
end
module RPG
class Event::Page
attr_accessor :button_labels
def button_labels
return @button_labels ||= []
end
alias :th_event_trigger_labels_list :list
def list
setup_trigger_labels unless @trigger_labels_set
th_event_trigger_labels_list
end
def setup_trigger_labels
@trigger_labels_set = true
nulls = []
if @trigger < 3
@list.each_with_index do |cmd, index|
if cmd.code == 118
label = cmd.parameters[0]
# Check for extra buttons
if trigger_label?(label)
nulls << index
end
end
end
end
# insert "exit event processing" before each "event branch"
nulls.reverse.each {|index|
@list.insert(index, RPG::EventCommand.new(115))
}
end
def trigger_label?(label)
if label =~ TH::Event_Trigger_Labels::Button_Regex
self.button_labels << $1.to_sym
return true
end
return false
end
end
end
class Game_Player < Game_Character
#-----------------------------------------------------------------------------
# Alias. Try to avoid hardcoding it
#-----------------------------------------------------------------------------
alias :th_trigger_labels_nonmoving :update_nonmoving
def update_nonmoving(last_moving)
return if $game_map.interpreter.running?
if trigger_conditions_met?
pre_trigger_event_processing
triggered = check_event_label_trigger
post_trigger_event_processing(triggered)
end
th_trigger_labels_nonmoving(last_moving)
end
def trigger_conditions_met?
movable? && Input.any_key_pressed?
end
#-----------------------------------------------------------------------------
#
#-----------------------------------------------------------------------------
def pre_trigger_event_processing
end
#-----------------------------------------------------------------------------
# New. Clean up.
#-----------------------------------------------------------------------------
def post_trigger_event_processing(triggered)
end
#-----------------------------------------------------------------------------
# New. Check for any valid events in the area
#-----------------------------------------------------------------------------
def check_event_label_trigger
positions_to_check_for_event.each do |x, y|
$game_map.events_xy(x, y).each do |event|
label = event.check_trigger_label
# If no label was found, check next event
next unless label
# If the event can run, insert a jump to label command at
# the beginning before running it
if check_action_event
text = [label]
command = RPG::EventCommand.new(119, 0, text)
event.list.insert(0, command)
return true
end
end
end
return false
end
#-----------------------------------------------------------------------------
# New. Positions to check events
#-----------------------------------------------------------------------------
def positions_to_check_for_event
positions = [[@x, @y]]
x2 = $game_map.round_x_with_direction(@x, @direction)
y2 = $game_map.round_y_with_direction(@y, @direction)
positions << [x2, y2]
return positions unless $game_map.counter?(x2, y2)
x3 = $game_map.round_x_with_direction(x2, @direction)
y3 = $game_map.round_y_with_direction(y2, @direction)
positions << [x3, y3]
return positions
end
end
class Game_Event
attr_reader :page
attr_reader :erased
attr_accessor :list
alias :th_event_trigger_labels_setup_page_settings :setup_page_settings
def setup_page_settings(*args)
th_event_trigger_labels_setup_page_settings
@list = Marshal.load(Marshal.dump(@page.list.clone))
end
def check_trigger_label
return nil if @erased
label = get_trigger_label
if label
# Reset the commands (Since we maybe have inserted a jump command before)
@needs_reset = true
end
return label
end
def get_trigger_label
label = check_button_trigger
return label if label
end
#-----------------------------------------------------------------------------
# New. Check whether the button triggers the event
#-----------------------------------------------------------------------------
def check_button_trigger
return unless button_trigger_met?
@page.button_labels.each do |button|
if Input.trigger?(button)
return "#{TH::Event_Trigger_Labels::Button_Format}(:#{button})"
end
end
return nil
end
def button_trigger_met?
return false unless @page
return false if @page.button_labels.empty?
return true
end
alias :th_event_trigger_labels_update :update
def update
th_event_trigger_labels_update
reset_page if @needs_reset
end
def reset_page
@needs_reset = false
@list = Marshal.load(Marshal.dump(@page.list.clone))
end
end

View file

@ -0,0 +1,110 @@
=begin
#===============================================================================
Title: Simple Audio Encryption MKXP-Z
Author: Taeyeon Mori
Date: Feb 23, 2022
Original Title: Simple Audio Encryption
Original Author: Hime
Original Date: Mar 22, 2014
Original URL: http://www.himeworks.com/2014/03/21/simple-audio-encryption/
--------------------------------------------------------------------------------
** Original Terms of Use
* Free to use in non-commercial projects
* Contact me for commercial use
* No real support. The script is provided as-is
* Will do bug fixes, but no compatibility patches
* Features may be requested but no guarantees, especially if it is non-trivial
* Credits to Hime Works in your project
* Preserve this header
#===============================================================================
=end
$imported = {} if $imported.nil?
$imported[:TH_SimpleAudioEncryption] = true
module TH
module Crypt
@@video_extensions = [".ogv"]
@@audio_extensions = ["", ".ogg", ".mp3", ".mid", ".wav"]
@@cache = {}
def self.find_real_path(path, exts)
# return unmodified if we can verify that it exists outside archive
return path if exts.any? {|ext| File.exist? (path + ext)}
exts.each do|ext|
# There is no way to check if a file exists in the archive from Ruby
# So we try to load it to a string and see if it fails.
# This is expensive so make sure to cache the result of this method
candidate = "Data/" + path + ext
begin
# MKXP-Z extension
load_data candidate, true
rescue
next
end
Preload.print "TH_SAE: Found #{path} at #{candidate}"
return candidate
end
# return nil if not found
return nil
end
def self.real_path(path, exts)
# Check cache
real_path = @@cache[path]
return real_path unless real_path.nil?
# Try to find
real_path = self.find_real_path path, exts
# Just fall back to the original path if not found
# Otherwise, we'll repeat the expensive lookup
# whenever this path comes up
real_path = path if real_path.nil?
# Save to cache
@@cache[path] = real_path
return real_path
end
def self.real_audio_path(path)
self.real_path path, @@audio_extensions
end
def self.real_video_path(path)
self.real_path path, @@video_extensions
end
end
end
module Audio
class << self
alias :th_simple_audio_decryption_bgm_play :bgm_play
alias :th_simple_audio_decryption_bgs_play :bgs_play
alias :th_simple_audio_decryption_me_play :me_play
alias :th_simple_audio_decryption_se_play :se_play
end
def self.bgm_play(name, *args)
th_simple_audio_decryption_bgm_play(TH::Crypt::real_audio_path(name), *args)
end
def self.bgs_play(name, *args)
th_simple_audio_decryption_bgs_play(TH::Crypt::real_audio_path(name), *args)
end
def self.me_play(name, *args)
th_simple_audio_decryption_me_play(TH::Crypt::real_audio_path(name), *args)
end
def self.se_play(name, *args)
th_simple_audio_decryption_se_play(TH::Crypt::real_audio_path(name), *args)
end
end
module Graphics
class << self
alias :th_simple_audio_encryption_play_movie :play_movie
end
def self.play_movie(name, *args)
th_simple_audio_encryption_play_movie(TH::Crypt::real_video_path(name), *args)
end
end

View file

@ -0,0 +1,815 @@
#===============================================================================
# Custom Resolution
# Authors: ForeverZer0, KK20
# Version: 0.96b
# Date: 11.15.2013
#===============================================================================
# MKXP Port
# Authors: Taeyeon Mori
# Date: 01/30/2022
#
# Includes tilemap texture size workaround
Preload.require 'XP_TilemapOverrideLib.rb'
#===============================================================================
# KK20's Notes
#===============================================================================
# Introduction:
#
# This script is intended to create screen resolutions other than 640 x 480.
# The script comes with its own Tilemap rewrite in order to combat larger
# screen resolutions (because anything beyond 640 x 480 is not drawn).
#
# Instructions:
#
# Place script above 'Main'. Probably best to put it below all your other
# custom scripts.
# You will also need 'screenshot.dll' included in your project. You can find
# that in Fantasist's Transitions Pack linked below.
#
# Things to Consider:
#
# - Fullscreen will change the resolution back to 640 x 480. A solution is in
# the works.
# - Transitions do not work properly on larger resolutions. You can use a
# Transitions Add-Ons script if you want better transitions (otherwise, all
# you will get is the default fade in/out). Links listed below.
# - Custom scripts that draw windows to the screen will most likely need edits.
# - Larger resolutions = more processing power = more lag
#
# ***************************************************************************
# * THIS IS STILL A WORK IN PROGRESS; IF YOU FIND ANYTHING PLEASE REPORT IT *
# ***************************************************************************
#
# Links:
# - Fantasist's Transitions Pack (w/ screenshot.dll)
# http://forum.chaos-project.com/index.php/topic,1390.0.html
# - ForeverZer0's Add-ons
# http://forum.chaos-project.com/index.php/topic,7862.0.html
# - ThallionDarkshine's Add-ons
# http://forum.chaos-project.com/index.php/topic,12655.0.html
# - Drago Transition Pack
# http://forum.chaos-project.com/index.php/topic,13488.0.html
#
#===============================================================================
# ForeverZer0's Notes from v0.93 (outdated information)
#===============================================================================
# Introduction:
#
# My goal in creating this script was to create a system that allowed the user
# to set the screen size to something other than 640 x 480, but not have make
# huge sacrifices in compatibility and performance. Although the script is
# not simply Plug-and-Play, it is about as close as one can achieve with a
# script of this nature.
#
# Instructions:
#
# - Place the "screenshot.dll" from Fantasist's Transition Pack script, which
# can be found here: http://www.sendspace.com/file/yjd54h in your game folder
# - Place this script above main, below default scripts.
# - In my experience, unchecking "Reduce Screen Flickering" actually helps the
# screen not to flicker. Open menu with F1 while playing and set this to what
# you get the best results with.
#
# Features:
#
# - Totally re-written Tilemap and Plane class. Both classes were written to
# display the map across any screen size automatically. The Tilemap class
# is probably even more efficient than the original, which will help offset
# any increased lag due to using a larger screen size with more sprites
# being displayed.
# - Every possible autotile graphic (48 per autotile) will be cached for the
# next time that tile is used.
# - Autotile animation has been made as efficient as possible, with a system
# that stores their coodinates, but only if they change. This greatly reduces
# the number of iterations at each update.
# - System creates an external file to save pre-cached data priorities and
# autotiles. This will decrease any loading times even more, and only takes a
# second, depending on the number of maps you have.
# - User defined autotile animation speed. Can change with script calls.
# - Automatic re-sizing of Bitmaps and Viewports that are 640 x 480 to the
# defined resolution, unless explicitely over-ridden in the method call.
# The graphics themselves will not be resized, but any existing scripts that
# use the normal screen size will already be configured to display different
# sizes of graphics for transitions, battlebacks, pictures, fogs, etc.
# - Option to have a log file ouput each time the game is ran, which can alert
# you to possible errors with map sizes, etc.
#
# Issues/Bugs/Possible Bugs:
#
# - Graphic related scripts and your graphics will need to be customized to
# fit the new screen size, so this script is not for everyone.
# - The Z-axis for the Plane class, which is used for Fogs and Panoramas has
# been altered. It is now multiplied by 1000. This will likely be a minor
# issue for most, since this class is very rarely used except for Fogs and
# Panoramas, which are already far above and below respectfully.
# - Normal transitions using graphics cannot be used. With the exception of
# a standard fade, like that used when no graphic is defined will be used.
# Aside from that, only special transitions from Transition Pack can be
# used.
#===============================================================================
# Credits/Thanks:
# - ForeverZer0, for script.
# - Creators of the Transition Pack and Screenshot.dll
# - Selwyn, for base resolution script
# - KK20, for Version 0.94 and above and the Tilemap class
#===============================================================================
# CONFIGURATION
#===============================================================================
# FIXME: need to exfiltrate this in the preload patch process
SCREEN = [1024, 576]
# Define the resolution of the game screen. These values can be anything
# within reason. Centering, viewports, etc. will all be taken care of, but it
# is recommended that you use values divisible by 32 for best results.
UPDATE_COUNT = 8
# Define the number of frames between autotile updates. The lower the number,
# the faster the animations cycle. This can be changed in-game with the
# following script call: $game_map.autotile_speed = SPEED
RESOLUTION_LOG = true
# This will create a log in the Game directory each time the game is ran in
# DEBUG mode, which will list possible errors with map sizes, etc.
#===============================================================================
# ** Resolution
#===============================================================================
class Resolution
attr_reader :version
def initialize
# Define version.
@version = 0.96
# Resize screen
Graphics.resize_screen(SCREEN[0], SCREEN[1])
end
#--------------------------------------------------------------------------
def size
# Returns the screen size of the machine.
# FIXME: available in MKXP?
return [1920, 1080]
end
#--------------------------------------------------------------------------
def snapshot(filename = 'Data/snap', quality = 0)
# FILENAME = Filename that the picture will be saved as.
# FILETYPE = 0 = High Quality 1 = Low Quality (ignored)
Graphics.screenshot(filename)
end
#--------------------------------------------------------------------------
end
#===============================================================================
# ** RPG::Cache
#===============================================================================
module RPG::Cache
AUTO_INDEX = [
[27,28,33,34], [5,28,33,34], [27,6,33,34], [5,6,33,34],
[27,28,33,12], [5,28,33,12], [27,6,33,12], [5,6,33,12],
[27,28,11,34], [5,28,11,34], [27,6,11,34], [5,6,11,34],
[27,28,11,12], [5,28,11,12], [27,6,11,12], [5,6,11,12],
[25,26,31,32], [25,6,31,32], [25,26,31,12], [25,6,31,12],
[15,16,21,22], [15,16,21,12], [15,16,11,22], [15,16,11,12],
[29,30,35,36], [29,30,11,36], [5,30,35,36], [5,30,11,36],
[39,40,45,46], [5,40,45,46], [39,6,45,46], [5,6,45,46],
[25,30,31,36], [15,16,45,46], [13,14,19,20], [13,14,19,12],
[17,18,23,24], [17,18,11,24], [41,42,47,48], [5,42,47,48],
[37,38,43,44], [37,6,43,44], [13,18,19,24], [13,14,43,44],
[37,42,43,48], [17,18,47,48], [13,18,43,48], [1,2,7,8]
]
def self.autotile(filename)
key = "Graphics/Autotiles/#{filename}"
if !@cache.include?(key) || @cache[key].disposed?
# Cache the autotile graphic.
@cache[key] = (filename == '') ? Bitmap.new(128, 96) : Bitmap.new(key)
# Cache each configuration of this autotile.
new_bm = self.format_autotiles(@cache[key], filename)
@cache[key].dispose
@cache[key] = new_bm
end
return @cache[key]
end
def self.format_autotiles(bitmap, filename)
if bitmap.height > 32
frames = bitmap.width / 96
template = Bitmap.new(256*frames,192)
# Create a bitmap to use as a template for creation.
(0..frames-1).each{|frame|
(0...6).each {|i| (0...8).each {|j| AUTO_INDEX[8*i+j].each {|number|
number -= 1
x, y = 16 * (number % 6), 16 * (number / 6)
rect = Rect.new(x + (frame * 96), y, 16, 16)
template.blt((32 * j + x % 32) + (frame * 256), 32 * i + y % 32, bitmap, rect)
}}}}
return template
else
return bitmap
end
end
end
#===============================================================================
# ** Tilemap_DataTable
#===============================================================================
class Tilemap_DataTable
attr_accessor :updates
attr_accessor :table
def initialize(table)
@table = table
@updates = []
end
def updated
return @updates.size >= 1
end
def [](x,y=nil,z=nil)
return @table[x,y,z] unless z.nil?
return @table[x,y] unless y.nil?
return @table[x]
end
def []=(x,y,z=nil,t_id=nil)
@updates.push([x,y,z,t_id]) unless t_id.nil?
t_id.nil? ? (z.nil? ? @table[x] = y : @table[x,y] = z) : @table[x,y,z] = t_id
end
def xsize; return @table.xsize; end
def ysize; return @table.ysize; end
def zsize; return @table.zsize; end
def resize(x,y=nil,z=nil); @table.resize(x,y,z); end
end
#===============================================================================
# ** Tilemap
#===============================================================================
class Tilemap
attr_reader :tileset, :map_data, :ox, :oy, :viewport
attr_accessor :autotiles, :priorities
# +++ MKXP +++
def tileset=(value)
# Need to wrap tilesets that don't fit into texture
if value.mega?
@tileset = TileWrap::wrapTileset(value)
value.dispose
else
@tileset = value
end
end
def initialize(viewport)
# Initialize instance variables to store required data.
@viewport, @autotiles, @tile_sprites, @ox, @oy = viewport, [], [], 0, 0
@current_frame, @total_frames = [], []
@tilemap_drawn = false
@ox_oy_set = [false, false]
# Get priority data for this tileset from instance of Game_Map.
@priorities = $game_map.priorities
# Holds all the Sprite instances of animating tiles (keys based on tile's ID)
@animating_tiles = {}
# Game map's x/y location of the top left corner tile
@corner_tile_loc = [-1,-1]
end
#-----------------------------------------------------------------------------
# Initialize all tile sprites. Draws three sprites per (x,y).
#-----------------------------------------------------------------------------
def init_tiles
# Determine how many frames of animation this autotile has
for i in 0..6
bm = @autotiles[i]
if bm.nil?
@total_frames = 1
elsif bm.height > 32
@total_frames[i] = bm.width / 256
else
@total_frames[i] = bm.width / 32
end
@current_frame[i] = 0
end
# Turn on flag that the tilemap sprites have been initialized
@tilemap_drawn = true
@animating_tiles.clear
# Create a sprite and viewport to use for each priority level.
(0...((SCREEN[0]/32+2) * (SCREEN[1]/32+2))*3).each{|i|
@tile_sprites[i/3] = [] if @tile_sprites[i/3].nil?
@tile_sprites[i/3][i%3] = Sprite.new(@viewport) unless @tile_sprites[i/3][i%3].is_a?(Sprite)
# Rename to something shorter and easier to work with for below
tile = @tile_sprites[i/3][i%3]
# Assign tile's respective ID value
tile.tile_sprite_id = i
# Draw sprite at index location (ex. ID 0 should always be the top-left sprite)
tile.x = (i % ((SCREEN[0]/32+2)*3) / 3 * 32) - 32 + (@ox % 32)
tile.y = (i / ((SCREEN[0]/32+2)*3) * 32) - 32 + (@oy % 32)
map_x, map_y = (tile.x+@ox)/32, (tile.y+@oy)/32
@corner_tile_loc = [map_x, map_y] if i == 0
# If the tile happens to be drawn along the outside borders of the map
if map_x < 0 || map_x >= $game_map.width || map_y < 0 || map_y >= $game_map.height
update_tile_id tile, 0
else # Tile is actually on the map
update_tile_id tile, @map_data[map_x, map_y, i%3]
end
}
# Sprite ID located at top left corner (ranges from 0..map_width * map_height
@corner_index = 0
end
# Common code for setting the tile by id
def update_tile_id(tile, tile_id)
if tile_id == 0 # empty tile
tile.z = 0
tile.bitmap = RPG::Cache.picture('')#@tileset
tile.src_rect.set(0,0,0,0)
return
end
if @priorities[tile_id] == 0
tile.z = 0
else
tile.z = tile.y + @priorities[tile_id] * 32 + 32
end
if tile_id >= 384 # non-autotile
tile.bitmap = @tileset
tile.src_rect.set(((tile_id - 384) % 8) * 32,
((tile_id - 384) / 8) * 32,
32, 32)
# Fix rect for possibly wrapped tileset
TileWrap.wrapRect! tile.src_rect
else # autotile
auto_id = tile_id/48-1
tile.bitmap = @autotiles[auto_id]
tile.src_rect.set(((tile_id % 48) % 8) * 32 + @current_frame[auto_id] * 256,
((tile_id % 48) / 8) * 32,
32, 32)
@animating_tiles[tile.tile_sprite_id] = tile if @total_frames[auto_id] > 1
end
end
#-----------------------------------------------------------------------------
# Makes update to ox and oy. Sprites out of range will be moved based on these
# two values.
#-----------------------------------------------------------------------------
def ox=(ox)
#
unless @tilemap_drawn
@ox = ox
@ox_oy_set[0] = true
return
end
return if @ox == ox
# Shift all tiles left or right by the difference
shift = @ox - ox
@tile_sprites.each {|set| set.each{|tile| tile.x += shift }}
@ox = ox
# Determine if columns need to be shifted
col_num = @corner_index
#return unless @tile_sprites[col_num][0].x <= -49 || @tile_sprites[col_num][0].x >= -17
while @tile_sprites[col_num][0].x <= -49 || @tile_sprites[col_num][0].x >= -17
@corner_tile_loc[0] += (shift < 0 ? 1 : -1)
modTileId = ((SCREEN[0]+64)*(SCREEN[1]+64))/1024
# If new ox is greater than old ox
if shift < 0
# Move all sprites in left column to the right side and change bitmaps
# and z-values
(0...(SCREEN[1]/32+2)).each{|n|
j = ((SCREEN[0]/32+2) * n + col_num) % modTileId
@tile_sprites[j].each_index{|i|
tile = @tile_sprites[j][i]
@animating_tiles.delete(tile.tile_sprite_id)
tile.x += 64 + SCREEN[0]
map_x, map_y = (tile.x+@ox)/32, (tile.y+@oy)/32
tile_id = @map_data[map_x,map_y,i]
if tile_id.nil?
tile.z = [map_y * 32, 0].max
tile.bitmap = RPG::Cache.picture('')
tile.src_rect.set(0,0,0,0)
next
end
update_tile_id tile, tile_id
}
}
# New corner should be the tile immediately right of the previous tile
col_num /= SCREEN[0]/32+2
col_num *= SCREEN[0]/32+2
@corner_index = (@corner_index + 1) % (SCREEN[0]/32+2) + col_num
else
# Shift right column to the left
# Gets the right column
row_index = col_num / (SCREEN[0]/32+2)
row_index *= (SCREEN[0]/32+2)
col_num = (@corner_index - 1) % (SCREEN[0]/32+2) + row_index
(0...(SCREEN[1]/32+2)).each{|n|
j = ((SCREEN[0]/32+2) * n + col_num) % modTileId
@tile_sprites[j].each_index{|i|
tile = @tile_sprites[j][i]
@animating_tiles.delete(tile.tile_sprite_id)
tile.x -= 64 + SCREEN[0]
map_x, map_y = (tile.x+@ox)/32, (tile.y+@oy)/32
tile_id = @map_data[map_x,map_y,i]
if tile_id.nil?
tile.z = [map_y * 32, 0].max
tile.bitmap = @tileset
tile.src_rect.set(0,0,0,0)
next
end
update_tile_id tile, tile_id
}
}
col_num /= SCREEN[0]/32+2
col_num *= SCREEN[0]/32+2
@corner_index = (@corner_index - 1) % (SCREEN[0]/32+2) + col_num
end
col_num = @corner_index
end #end of while
end
#-----------------------------------------------------------------------------
def oy=(oy)
#
unless @tilemap_drawn
@oy = oy
@ox_oy_set[1] = true
return
end
return if @oy == oy
# Shift all tiles up or down by the difference, and change z-value
shift = @oy - oy
@tile_sprites.each {|set| set.each{|tile| tile.y += shift; tile.z += shift unless tile.z == 0 }}
@oy = oy
# Determine if rows need to be shifted
row_num = @corner_index
#return unless @tile_sprites[row_num][0].y <= -49 || @tile_sprites[row_num][0].y >= -17
while @tile_sprites[row_num][0].y <= -49 || @tile_sprites[row_num][0].y >= -17
# Needed for resetting the new corner index much later.
modTileId = ((SCREEN[0]+64)*(SCREEN[1]+64))/1024
@corner_tile_loc[1] += (shift < 0 ? 1 : -1)
# If new oy is greater than old oy
if shift < 0
row_num /= SCREEN[0]/32+2
row_num *= SCREEN[0]/32+2
# Move all sprites in top row to the bottom side and change bitmaps
# and z-values
(0...(SCREEN[0]/32+2)).each{|n|
# Run through each triad of sprites from left to right
j = n + row_num
@tile_sprites[j].each_index{|i|
# Get each individual tile on each layer
tile = @tile_sprites[j][i]
@animating_tiles.delete(tile.tile_sprite_id)
tile.y += 64 + SCREEN[1]
# Determine what map coordinate this tile now resides at...
map_x, map_y = (tile.x+@ox)/32, (tile.y+@oy)/32
# ...and get its tile_id
tile_id = @map_data[map_x,map_y,i]
# If no tile exists here (effectively out of array bounds)
if tile_id.nil?
tile.z = [map_y * 32, 0].max
tile.bitmap = RPG::Cache.picture('')
tile.src_rect.set(0,0,0,0)
next
end
update_tile_id tile, tile_id
}
}
@corner_index = (@corner_index + (SCREEN[0]/32+2)) % modTileId
else
row_num = (@corner_index - (SCREEN[0]/32+2)) % modTileId
row_num /= SCREEN[0]/32+2
row_num *= SCREEN[0]/32+2
(0...(SCREEN[0]/32+2)).each{|n|
# Run through each triad of sprites from left to right
j = n + row_num
@tile_sprites[j].each_index{|i|
# Get each individual tile on each layer
tile = @tile_sprites[j][i]
@animating_tiles.delete(tile.tile_sprite_id)
tile.y -= 64 + SCREEN[1]
# Determine what map coordinate this tile now resides at...
map_x, map_y = (tile.x+@ox)/32, (tile.y+@oy)/32
# ...and get its tile_id
tile_id = @map_data[map_x,map_y,i]
# If no tile exists here (effectively out of array bounds)
if tile_id.nil?
tile.z = [map_y * 32, 0].max
tile.bitmap = RPG::Cache.picture('')
tile.src_rect.set(0,0,0,0)
next
end
update_tile_id tile, tile_id
}
}
@corner_index = (@corner_index - (SCREEN[0]/32+2)) % modTileId
end
row_num = @corner_index
end # end of while
end
#-----------------------------------------------------------------------------
# Dispose all the tile sprites
#-----------------------------------------------------------------------------
def dispose
# Dispose all of the sprites
@tile_sprites.each {|set| set.each{|tile| tile.dispose }}
@tile_sprites.clear
@animating_tiles.clear
end
#-----------------------------------------------------------------------------
# Set map data
#-----------------------------------------------------------------------------
def map_data=(data)
# Set the map data to new class
if data.is_a?(Tilemap_DataTable)
@map_data = data
else
@map_data = Tilemap_DataTable.new(data)
end
@map_data.table = @map_data.table.clone
@map_data.updates = []
@animating_tiles.clear
@tilemap_drawn = false
end
#-----------------------------------------------------------------------------
# Update the tile sprites; make changes to the map_data and update autotiles
#-----------------------------------------------------------------------------
def update
# Can't update anything if the ox and oy have not yet been set
return if @ox_oy_set != [true, true]
# If the tilemap sprites have not been initialized, GO DO IT
if !@tilemap_drawn
init_tiles
end
# If made any changes to $game_map.data, the proper graphics will be drawn
if @map_data.updated
@map_data.updates.each{|item|
x,y,z,tile_id = item
# If this changed tile is visible on screen
if x.between?(@corner_tile_loc[0], @corner_tile_loc[0]+(SCREEN[0]/32 + 1)) and
y.between?(@corner_tile_loc[1], @corner_tile_loc[1]+(SCREEN[1]/32 + 1))
x_dif = x - @corner_tile_loc[0]
y_dif = y - @corner_tile_loc[1]
id = @corner_index + x_dif
id -= SCREEN[0]/32+2 if id/(SCREEN[0]/32+2) > @corner_index/(SCREEN[0]/32+2)
id += y_dif * (SCREEN[0]/32+2)
id -= (SCREEN[0]/32+2)*(SCREEN[1]/32+2) if id >= (SCREEN[0]/32+2)*(SCREEN[1]/32+2)
tile = @tile_sprites[id][z]
@animating_tiles.delete(tile.tile_sprite_id)
update_tile_id tile, tile_id
end
}
@map_data.updates = []
end
# Update the sprites.
if Graphics.frame_count % $game_map.autotile_speed == 0
# Increase current frame of tile by one, looping by width.
for i in 0..6
@current_frame[i] = (@current_frame[i] + 1) % @total_frames[i]
end
@animating_tiles.each_value{|tile|
frames = tile.bitmap.width
tile.src_rect.set((tile.src_rect.x + 256) % frames, tile.src_rect.y, 32, 32)
}
end
end
end
#===============================================================================
# Game_Map
#===============================================================================
class Game_Map
attr_reader :tile_size, :autotile_speed, :autotile_data, :priority_data
alias zer0_load_autotile_data_init initialize
def initialize
# Call original method.
zer0_load_autotile_data_init
# Store the screen dimensions in tiles to save on calculations later.
@tile_size = [SCREEN[0], SCREEN[1]].collect {|n| (n / 32.0).ceil }
@autotile_speed = UPDATE_COUNT
end
alias zer0_map_edge_setup setup
def setup(map_id)
# Call original method.
zer0_map_edge_setup(map_id)
# Change Map's data into a special Table class
@map.data = Tilemap_DataTable.new(@map.data)
# Find the displayed area of the map in tiles. No calcualting every step.
@map_edge = [self.width - @tile_size[0], self.height - @tile_size[1]]
@map_edge.collect! {|size| size * 128 }
end
def scroll_down(distance)
# Find point that the map edge meets the screen edge, using custom size.
@display_y = [@display_y + distance, @map_edge[1]].min
end
def scroll_right(distance)
# Find point that the map edge meets the screen edge, using custom size.
@display_x = [@display_x + distance, @map_edge[0]].min
end
def autotile_speed=(speed)
# Keep the speed above 0 to prevent the ZeroDivision Error.
@autotile_speed = speed
@autotile_speed = 1 if @autotile_speed < 1
end
end
#===============================================================================
# ** Game_Player
#===============================================================================
class Game_Player
CENTER_X = ((SCREEN[0] / 2) - 16) * 4 # Center screen x-coordinate * 4
CENTER_Y = ((SCREEN[1] / 2) - 16) * 4 # Center screen y-coordinate * 4
def center(x, y)
# Recalculate the screen center based on the new resolution.
max_x = ($game_map.width - $game_map.tile_size[0]) * 128
max_y = ($game_map.height - $game_map.tile_size[1]) * 128
$game_map.display_x = [0, [x * 128 - CENTER_X, max_x].min].max
$game_map.display_y = [0, [y * 128 - CENTER_Y, max_y].min].max
end
end
#===============================================================================
# ** Sprite
#===============================================================================
class Sprite
attr_accessor :tile_sprite_id
alias tile_sprite_id_init initialize
def initialize(view = nil)
# No defined ID
@tile_sprite_id = nil
# Call original method.
tile_sprite_id_init(view)
end
end
#===============================================================================
# ** Viewport
#===============================================================================
class Viewport
alias zer0_viewport_resize_init initialize
def initialize(x=0, y=0, width=SCREEN[0], height=SCREEN[1], override=false)
if x.is_a?(Rect)
# If first argument is a Rectangle, just use it as the argument.
zer0_viewport_resize_init(x)
elsif [x, y, width, height] == [0, 0, 640, 480] && !override
# Resize fullscreen viewport, unless explicitly overridden.
zer0_viewport_resize_init(Rect.new(0, 0, SCREEN[0], SCREEN[1]))
else
# Call method normally.
zer0_viewport_resize_init(Rect.new(x, y, width, height))
end
end
def resize(*args)
# Resize the viewport. Can call with (X, Y, WIDTH, HEIGHT) or (RECT).
self.rect = args[0].is_a?(Rect) ? args[0] : Rect.new(*args)
end
end
#===============================================================================
# ** Plane
#===============================================================================
# class Plane < Sprite
# def z=(z)
# # Change the Z value of the viewport, not the sprite.
# super(z * 1000)
# end
# def ox=(ox)
# return if @bitmap == nil
# # Have viewport stay in loop on X-axis.
# super(ox % @bitmap.width)
# end
# def oy=(oy)
# return if @bitmap == nil
# # Have viewport stay in loop on Y-axis.
# super(oy % @bitmap.height)
# end
# def bitmap
# # Return the single bitmap, before it was tiled.
# return @bitmap
# end
# def bitmap=(tile)
# @bitmap = tile
# # Calculate the number of tiles it takes to span screen in both directions.
# xx = 1 + (SCREEN[0].to_f / tile.width).ceil
# yy = 1 + (SCREEN[1].to_f / tile.height).ceil
# # Create appropriately sized bitmap, then tile across it with source image.
# plane = Bitmap.new(@bitmap.width * xx, @bitmap.height * yy)
# (0..xx).each {|x| (0..yy).each {|y|
# plane.blt(x * @bitmap.width, y * @bitmap.height, @bitmap, @bitmap.rect)
# }}
# # Set the bitmap to the sprite through its super class (Sprite).
# super(plane)
# end
# # Redefine methods dealing with coordinates (defined in super) to do nothing.
# def x; end
# def y; end
# def x=(x); end
# def y=(y); end
# end
#===============================================================================
# ** Integer
#===============================================================================
class Integer
def gcd(num)
# Returns the greatest common denominator of self and num.
min, max = self.abs, num.abs
while min > 0
tmp = min
min = max % min
max = tmp
end
return max
end
def lcm(num)
# Returns the lowest common multiple of self and num.
return [self, num].include?(0) ? 0 : (self / self.gcd(num) * num).abs
end
end
#===============================================================================
# ** Resolution Log
#===============================================================================
if RESOLUTION_LOG
undersize, mapinfo = [], load_data('Data/MapInfos.rxdata')
# Create a text file and write the header.
file = File.open('Resolution Log.txt', 'wb')
file.write("[RESOLUTION LOG]\r\n\r\n")
time = Time.now.strftime("%x at %I:%M:%S %p")
file.write(" Logged on #{time}\r\n\r\n")
lcm = SCREEN[0].lcm(SCREEN[1]).to_f
aspect = [(lcm / SCREEN[1]), (lcm / SCREEN[0])].collect {|num| num.round }
file.write("RESOLUTION:\r\n #{SCREEN[0].to_i} x #{SCREEN[1].to_i}\r\n")
file.write("ASPECT RATIO:\r\n #{aspect[0]}:#{aspect[1]}\r\n")
file.write("MINIMUM MAP SIZE:\r\n #{(SCREEN[0] / 32).ceil} x #{(SCREEN[1] / 32).ceil}\r\n\r\n")
file.write("UNDERSIZED MAPS:\r\n")
mapinfo.keys.each {|key|
map = load_data(sprintf("Data/Map%03d.rxdata", key))
next if map.width*32 >= SCREEN[0] && map.height*32 >= SCREEN[1]
undersize.push(key)
}
unless undersize.empty?
file.write("The following maps are too small for the defined resolution. They should be adjusted to prevent graphical errors.\r\n\r\n")
undersize.sort.each {|id| file.write(" MAP[#{id}]: #{mapinfo[id].name}\r\n") }
file.write("\r\n")
else
file.write(' All maps are sized correctly.')
end
file.close
end
# Call the resolution, setting it to a global variable for plug-ins.
$resolution = Resolution.new

View file

@ -0,0 +1,61 @@
# Zeus81 Fullscreen++ MKXP API Shim
# Authors: Taeyeon Mori
Preload.require 'PreloadIni.rb'
# Fullscreen++ v2.2 for VX and VXace by Zeus81
# Free for non commercial and commercial use
# Licence : http://creativecommons.org/licenses/by-sa/3.0/
# Contact : zeusex81@gmail.com
# (fr) Manuel d'utilisation : http://pastebin.com/raw.php?i=1TQfMnVJ
# (en) User Guide : http://pastebin.com/raw.php?i=EgnWt9ur
$imported ||= {}
$imported[:Zeus_Fullscreen] = __FILE__
class << Graphics
Disable_VX_Fullscreen = true
unless method_defined?(:zeus_fullscreen_update)
alias zeus_fullscreen_update update
end
def load_fullscreen_settings
fullscreen = (Preload::Ini.readIniString('./Game.ini', 'Fullscreen++', 'Fullscreen') || '0') == '1'
end
def save_fullscreen_settings
Preload::Ini.writeIniString('./Game.ini', 'Fullscreen++', 'Fullscreen', fullscreen ? '1' : '0')
end
def fullscreen?
fullscreen
end
def vx_fullscreen?
false
end
def toggle_fullscreen
fullscreen = !fullscreen
end
def toggle_vx_fullscreen
end
def vx_fullscreen_mode
end
def fullscreen_mode
fullscreen = true
end
def windowed_mode
fullscreen = false
end
def toggle_ratio
end
def ratio
1
end
def ratio=(r)
end
def update
zeus_fullscreen_update
toggle_fullscreen if Input.trigger?(Input::F5)
end
end
Graphics.load_fullscreen_settings

View file

@ -0,0 +1,530 @@
# Map Effects v1.4.1 for VX and VXace by Zeus81
# €30 for commercial use
# Licence : http://creativecommons.org/licenses/by-nc-nd/4.0/
# Contact : zeusex81@gmail.com
# (fr) Manuel d'utilisation : https://www.dropbox.com/s/lb1d3q9jmx53taf/Map%20Effects%20Doc%20Fr.txt
# (en) User Guide : https://www.dropbox.com/s/sk3uwq2bleoxr7s/Map%20Effects%20Doc%20En.txt
# Demo : https://www.dropbox.com/s/2ex6906dyehl7an/Map%20Effects.zip
$imported ||= {}
$imported[:Zeus_Map_Effects] = __FILE__
def xp?() false end ; def vx?() false end ; def vxace?() false end
RUBY_VERSION == '1.8.1' ? defined?(Hangup) ?
def xp?() true end : def vx?() true end : def vxace?() true end
class << Graphics
def snap_elements_to_bitmap(*elements)
if !@snap_elements_back or @snap_elements_back.disposed?
@snap_elements_back = Sprite.new
@snap_elements_back.bitmap = Bitmap.new(1, 1)
@snap_elements_back.bitmap.set_pixel(0, 0, Color.new(0, 0, 0))
@snap_elements_back.z = 0x0FFF_FFFF
end
@snap_elements_back.zoom_x = width
@snap_elements_back.zoom_y = height
@snap_elements_back.visible = true
elements.each {|element| element.z += 0x1FFF_FFFF}
bmp = snap_to_bitmap rescue retry
@snap_elements_back.visible = false
elements.each {|element| element.z -= 0x1FFF_FFFF}
return bmp
end
end
module Math
module_function
def min(x, y) x < y ? x : y end
def max(x, y) x < y ? y : x end
def middle(min, x, max) x < max ? x < min ? min : x : max end
end
module Zeus
module Animation
def animate(variable, target_value, duration=0, ext=nil)
@za_animations ||= {}
base_value = Marshal.load(Marshal.dump(instance_variable_get(variable)))
if duration < 1
update_animation_value(variable, base_value, target_value, 1, 1, ext)
@za_animations.delete(variable)
else
@za_animations[variable] = [base_value, target_value, 0, duration.to_i, ext]
end
end
def animating?
@za_animations and !@za_animations.empty?
end
def clear_animations
@za_animations and @za_animations.clear
end
def memorize_animations(variables = instance_variables)
data = {}
variables.each {|var| data[var.to_sym] = instance_variable_get(var)}
data.delete(:@za_memorize)
@za_memorize = Marshal.dump(data)
end
def restore_animations
return unless @za_memorize
Marshal.load(@za_memorize).each {|var,value| instance_variable_set(var,value)}
end
def update_animations
return unless @za_animations
@za_animations.delete_if do |variable, data|
data[2] += 1
update_animation_value(variable, *data)
data[2] == data[3]
end
end
private
def calculate_next_value(base_value, target_value, duration, duration_total)
base_value + (target_value - base_value) * duration / duration_total
end
def update_animation_value(variable, base_value, target_value, duration, duration_total, ext)
method_name = "update_animation_variable_#{variable.to_s[1..-1]}"
method_name = "update_animation_#{base_value.class}" unless respond_to?(method_name)
send(method_name, variable, base_value, target_value, duration, duration_total, ext)
end
def update_animation_Color(variable, base_value, target_value, duration, duration_total, ext)
value = instance_variable_get(variable)
value.red = calculate_next_value(base_value.red , target_value.red , duration, duration_total)
value.green = calculate_next_value(base_value.green, target_value.green, duration, duration_total)
value.blue = calculate_next_value(base_value.blue , target_value.blue , duration, duration_total)
value.alpha = calculate_next_value(base_value.alpha, target_value.alpha, duration, duration_total)
end
def update_animation_Tone(variable, base_value, target_value, duration, duration_total, ext)
value = instance_variable_get(variable)
value.red = calculate_next_value(base_value.red , target_value.red , duration, duration_total)
value.green = calculate_next_value(base_value.green, target_value.green, duration, duration_total)
value.blue = calculate_next_value(base_value.blue , target_value.blue , duration, duration_total)
value.gray = calculate_next_value(base_value.gray , target_value.gray , duration, duration_total)
end
def update_animation_Float(variable, base_value, target_value, duration, duration_total, ext)
value = calculate_next_value(base_value, target_value, duration, duration_total)
instance_variable_set(variable, value)
end
alias update_animation_Fixnum update_animation_Float
alias update_animation_Bignum update_animation_Float
end
end
class Game_Map_Effects
include Zeus::Animation
attr_accessor :active, :refresh_rate, :back, :x, :y, :ox, :oy, :angle,
:zoom_x, :zoom_y, :mirror, :opacity, :blend_type, :color, :tone,
:hue, :wave_amp, :wave_length, :wave_speed, :wave_phase,
:pixelize, :blur_division, :blur_fade, :blur_animation,
:gaussian_blur_length, :linear_blur_angle, :linear_blur_length,
:radial_blur_angle, :zoom_blur_length, :motion_blur_rate
def initialize
@active = true
@refresh_rate = 30.0
clear
end
def clear
@back = false
@x = @ox = Graphics.width / 2
@y = @oy = Graphics.height / 2
@zoom_x = 1.0
@zoom_y = 1.0
@zoom2 = Math.sqrt(100.0)
@angle = 0.0
@wave_amp = 0.0
@wave_length = 180
@wave_speed = 360
@wave_phase = 0.0
@mirror = false
@opacity = 255
@blend_type = 0
@color ||= Color.new(0, 0, 0, 0)
@color.set(0, 0, 0, 0)
@tone ||= Tone.new(0, 0, 0, 0)
@tone.set(0, 0, 0, 0)
@hue = 0
@pixelize = 1.0
@pixelize2 = Math.sqrt(100.0)
@blur_division = 4.0
@blur_fade = 1.0
@blur_animation = 0.0
@gaussian_blur_length = 0.0
@linear_blur_angle = 0.0
@linear_blur_length = 0.0
@radial_blur_angle = 0.0
@zoom_blur_length = 0.0
@motion_blur_rate = 0.0
clear_animations
end
alias memorize memorize_animations
alias restore restore_animations
alias update update_animations
def active?
return false unless @active
animating? or blur? or @mirror or @blend_type != 0 or
@zoom_x != 1 or @zoom_y != 1 or @pixelize > 1 or
@angle % 360 != 0 or @hue.to_i % 360 != 0 or @color.alpha != 0 or
@tone.red != 0 or @tone.green != 0 or @tone.blue != 0 or @tone.gray != 0 or
(@wave_amp * @zoom_x >= 1 and @wave_length * @zoom_y >= 1)
end
def blur?
return false if @blur_division < 1
@gaussian_blur_length != 0 or @linear_blur_length != 0 or
@radial_blur_angle != 0 or @zoom_blur_length != 0 or @motion_blur_rate != 0
end
def refresh_bitmap?
@refresh_rate > 0 and
Graphics.frame_count % (Graphics.frame_rate / @refresh_rate.to_f) < 1
end
def tilemap_wave_sync(tilemap_oy)
return 0 if @wave_length == 0
tilemap_oy * @wave_speed / @wave_length.to_f
end
def blur_animation_offset
return 0 if @blur_animation == 0
1 - @blur_animation * Graphics.frame_count / Graphics.frame_rate.to_f % 1
end
def refresh_motion_blur?
@blur_division >= 1 and @motion_blur_rate > 0 and
Graphics.frame_count % @motion_blur_rate < 1
end
def set_origin(x, y, duration=0)
x = x * Graphics.width / 100
y = y * Graphics.height / 100
animate(:@x , x, duration)
animate(:@y , y, duration)
animate(:@ox, x, duration)
animate(:@oy, y, duration)
end
def set_zoom(zoom, duration=0, center_on_player=true)
zoom = Math.sqrt(Math.max(1, zoom))
animate(:@zoom2, zoom, duration, center_on_player)
end
def update_animation_variable_zoom2(variable, base_value, target_value, duration, duration_total, center_on_player)
update_animation_Float(variable, base_value, target_value, duration, duration_total, nil)
@zoom_y = @zoom_x = @zoom2 ** 2 / 100.0
display_ratio = Game_Map::DisplayRatio.to_f
if center_on_player
x = $game_player.real_x / display_ratio
y = $game_player.real_y / display_ratio
else
x = $game_map.display_x / display_ratio + $game_map.screen_tile_x / 2
y = $game_map.display_y / display_ratio + $game_map.screen_tile_y / 2
end
$game_player.center(x, y)
end
def set_angle(angle, duration=0)
animate(:@angle, angle, duration)
end
def set_opacity(opacity, duration=0)
opacity = opacity * 255 / 100
animate(:@opacity, opacity, duration)
end
def set_color(red, green, blue, alpha, duration=0)
animate(:@color, Color.new(red, green, blue, alpha), duration)
end
def set_tone(red, green, blue, gray, duration=0)
animate(:@tone, Tone.new(red, green, blue, gray), duration)
end
def set_hue(hue, duration=0)
animate(:@hue, hue, duration)
end
def set_wave(amp, length, speed, duration=0)
animate(:@wave_amp , amp , duration)
animate(:@wave_length, length, duration)
animate(:@wave_speed , speed , duration)
end
def set_pixelize(pixelize, duration=0)
pixelize = Math.sqrt(Math.max(100, pixelize))
animate(:@pixelize2, pixelize, duration)
end
def update_animation_variable_pixelize2(variable, base_value, target_value, duration, duration_total, ext)
update_animation_Float(variable, base_value, target_value, duration, duration_total, ext)
@pixelize = @pixelize2 ** 2 / 100.0
end
def setup_blur(division, fade, animation, duration=0)
division = Math.middle(0, division, 16)
animate(:@blur_division , division , duration)
animate(:@blur_fade , fade , duration)
animate(:@blur_animation, animation, duration)
end
def set_gaussian_blur(length, duration=0)
animate(:@gaussian_blur_length, length, duration)
end
def set_linear_blur(angle, length, duration=0)
animate(:@linear_blur_angle , angle , duration)
animate(:@linear_blur_length, length, duration)
end
def set_radial_blur(angle, duration=0)
animate(:@radial_blur_angle, angle, duration)
end
def set_zoom_blur(zoom, duration=0)
length = Math.max(1, zoom) / 100.0 - 1
animate(:@zoom_blur_length, length, duration)
end
def set_motion_blur(rate, duration=0)
animate(:@motion_blur_rate, rate, duration)
end
end
class Spriteset_Map_Effects
Blur_Offset = [[0.7,0.7], [-0.7,-0.7], [-0.7,0.7], [0.7,-0.7],
[0,1], [0,-1], [1,0], [-1,0]]
def initialize(*viewports)
@map_viewports = viewports
@viewport = Viewport.new(viewports[0].rect)
@viewport.z = viewports[0].z
@viewport.visible = false
@effects_sprites = []
@effects_bitmaps = []
@data = $game_map.effects
end
def dispose(dispose_viewport=true)
@effects_sprites.each {|sprite| sprite.dispose}
@effects_sprites.clear
@effects_bitmaps.each {|bitmap| bitmap.dispose if bitmap}
@effects_bitmaps.clear
@pixelize_bitmap.dispose if @pixelize_bitmap
@pixelize_bitmap = nil
@back_sprite.dispose if @back_sprite
@back_sprite = nil
if dispose_viewport
@viewport.dispose
else
@viewport.visible = false
@map_viewports.each {|viewport| viewport.visible = true}
end
end
def update(tilemap_oy = 0)
unless @data.active?
dispose(false) if @viewport.visible
return
end
@viewport.visible = true
@motion_blur_refresh ||= @data.refresh_motion_blur?
refresh_sprites
if !@effects_bitmaps[0] or @data.refresh_bitmap?
refresh_bitmaps
refresh_pixelize
end
refresh_back
wave_sync = @data.tilemap_wave_sync(tilemap_oy)
blur_offset = @data.blur_animation_offset
@effects_sprites.each_with_index do |sprite, id|
update_effects(sprite, id, wave_sync)
update_pixelize(sprite) if @pixelize_bitmap
update_blur(sprite, id, blur_offset) if id > 0
end
@data.wave_phase = @effects_sprites[0].wave_phase - wave_sync
end
def refresh_sprites
n = (@data.blur? ? @data.blur_division.to_i+1 : 1) - @effects_sprites.size
n.times {@effects_sprites << Sprite.new(@viewport)}
(-n).times {@effects_sprites.pop.dispose}
end
def refresh_bitmaps
n = (@data.motion_blur_rate == 0 ? 1 : @effects_sprites.size) - @effects_bitmaps.size
n.times {@effects_bitmaps << nil}
(-n).times {bmp = @effects_bitmaps.pop and bmp.dispose}
@map_viewports.each {|viewport| viewport.visible = true}
@effects_bitmaps.unshift(@effects_bitmaps.pop) if @motion_blur_refresh
@effects_bitmaps[0].dispose if @effects_bitmaps[0]
@effects_bitmaps[0] = Graphics.snap_elements_to_bitmap(*@map_viewports)
@effects_bitmaps[0].hue_change(@data.hue % 360) if @data.hue.to_i % 360 != 0
@map_viewports.each {|viewport| viewport.visible = false}
@motion_blur_refresh = false
end
def refresh_pixelize
if @data.pixelize > 1
bmp = @effects_bitmaps[0]
@pixelize_rect ||= Rect.new(0, 0, 0, 0)
@pixelize_rect.width = Math.max(1, bmp.width / @data.pixelize)
@pixelize_rect.height = Math.max(1, bmp.height / @data.pixelize)
@pixelize_bitmap ||= Bitmap.new(bmp.width, bmp.height)
@pixelize_bitmap.clear
@pixelize_bitmap.stretch_blt(@pixelize_rect, bmp, bmp.rect)
elsif @pixelize_bitmap
@pixelize_bitmap.dispose
@pixelize_bitmap = nil
end
end
def refresh_back
if @data.back
@back_sprite ||= Sprite.new(@viewport)
@back_sprite.bitmap = @effects_bitmaps[0]
elsif @back_sprite
@back_sprite.dispose
@back_sprite = nil
end
end
def update_effects(sprite, id, wave_sync)
sprite.bitmap = @effects_bitmaps[id] || @effects_bitmaps[0]
sprite.x = @data.x
sprite.y = @data.y
sprite.z = id + 1
sprite.ox = @data.ox
sprite.oy = @data.oy
sprite.zoom_x = @data.zoom_x
sprite.zoom_y = @data.zoom_y
sprite.angle = @data.angle % 360
sprite.wave_amp = @data.wave_amp * @data.zoom_x
sprite.wave_length = @data.wave_length * @data.zoom_y
sprite.wave_speed = @data.wave_speed * @data.zoom_y
sprite.wave_phase = @data.wave_phase + wave_sync
sprite.mirror = @data.mirror
sprite.opacity = @data.opacity
sprite.blend_type = @data.blend_type
sprite.color = @data.color
sprite.tone = @data.tone
sprite.update
end
def update_pixelize(sprite)
pzx = @pixelize_bitmap.width / @pixelize_rect.width.to_f
pzy = @pixelize_bitmap.height / @pixelize_rect.height.to_f
sprite.bitmap = @pixelize_bitmap
sprite.src_rect = @pixelize_rect
sprite.x -= sprite.ox - (sprite.ox /= pzx).to_i * pzx
sprite.y -= sprite.oy - (sprite.oy /= pzy).to_i * pzy
sprite.zoom_x *= pzx
sprite.zoom_y *= pzy
end
def update_blur(sprite, id, blur_offset)
update_blur_opacity(sprite, id-blur_offset)
update_gaussian_blur(sprite, id) if @data.gaussian_blur_length != 0
update_linear_blur(sprite, id-blur_offset) if @data.linear_blur_length != 0
update_radial_blur(sprite, id-blur_offset) if @data.radial_blur_angle != 0
update_zoom_blur(sprite, id-blur_offset) if @data.zoom_blur_length != 0
end
def update_blur_opacity(sprite, id)
sprite.opacity /= (id < 1 ? 2 : id+1) **
(1 + @data.blur_fade / (@data.blur_division*20.0))
end
def update_gaussian_blur(sprite, id)
box, boy = *Blur_Offset[(id-1)%8]
offset = ((id+3)/4) / ((@data.blur_division.to_i+3)/4).to_f *
@data.gaussian_blur_length
sprite.x += (offset.ceil * box).round
sprite.y += (offset.ceil * boy).round
end
def update_linear_blur(sprite, id)
radian = @data.linear_blur_angle * Math::PI / 180
offset = id * @data.linear_blur_length / @data.blur_division.to_f
sprite.x += offset * Math.cos( radian)
sprite.y += offset * Math.sin(-radian)
end
def update_zoom_blur(sprite, id)
zoom = 1 + id * @data.zoom_blur_length / @data.blur_division.to_f
sprite.zoom_x *= zoom
sprite.zoom_y *= zoom
end
def update_radial_blur(sprite, id)
sprite.angle += id * @data.radial_blur_angle / @data.blur_division.to_f
sprite.angle %= 360
end
end
class Game_Map
if vx?
def screen_tile_x() Graphics.width / 32 end
def screen_tile_y() Graphics.height / 32 end
DisplayRatio = 256
else
DisplayRatio = 1
end
def zoom_ox
return 0 unless effects.active and effects.zoom_x > 1
(1 - 1 / effects.zoom_x) * screen_tile_x / 2
end
def zoom_oy
return 0 unless effects.active and effects.zoom_y > 1
(1 - 1 / effects.zoom_y) * screen_tile_y / 2
end
def limit_x(x)
ox = zoom_ox
min = DisplayRatio * -ox
max = DisplayRatio * (width - screen_tile_x + ox)
x < max ? x < min ? min : x : max
end
def limit_y(y)
oy = zoom_oy
min = DisplayRatio * -oy
max = DisplayRatio * (height - screen_tile_y + oy)
y < max ? y < min ? min : y : max
end
def set_display_x(x)
x = loop_horizontal? ? x % (width * DisplayRatio) : limit_x(x)
@parallax_x += x - @display_x if @parallax_loop_x or !loop_horizontal?
@display_x = x
end
def set_display_y(y)
y = loop_vertical? ? y % (height * DisplayRatio) : limit_y(y)
@parallax_y += y - @display_y if @parallax_loop_y or !loop_vertical?
@display_y = y
end
def set_display_pos(x, y) set_display_x(x); set_display_y(y) end
def scroll_down(distance) set_display_y(@display_y + distance) end
def scroll_left(distance) set_display_x(@display_x - distance) end
def scroll_right(distance) set_display_x(@display_x + distance) end
def scroll_up(distance) set_display_y(@display_y - distance) end
def effects() @effects ||= Game_Map_Effects.new end
alias zeus_map_effects_update update
def update(*args)
zeus_map_effects_update(*args)
effects.update
end
end
class Game_Interpreter
def map_effects
$game_map.effects
end
end
class Game_Player
def center(x, y)
$game_map.set_display_pos(x*256-CENTER_X, y*256-CENTER_Y)
end
end if vx?
class Spriteset_Map
alias zeus_map_effects_update update
def update
zeus_map_effects_update
@map_effects ||= Spriteset_Map_Effects.new(@viewport1)
@map_effects.update(@tilemap.oy)
end
alias zeus_map_effects_dispose dispose
def dispose
zeus_map_effects_dispose
@map_effects.dispose
end
end
$imported[:Zeus_Weather_Viewport] ||= __FILE__
if $imported[:Zeus_Weather_Viewport] == __FILE__
class Spriteset_Map
alias zeus_weather_viewport_create_weather create_weather
def create_weather
zeus_weather_viewport_create_weather
@weather.weather_viewport = @viewport1
end
end
class Spriteset_Weather
if vx?
def weather_viewport=(viewport)
for sprite in @sprites
sprite.viewport = viewport
sprite.z = 0x8000
end
end
else
attr_accessor :weather_viewport
alias zeus_weather_viewport_add_sprite add_sprite
def add_sprite
zeus_weather_viewport_add_sprite
@sprites[-1].viewport = @weather_viewport
@sprites[-1].z = 0x8000
end
end
end
end

View file

@ -0,0 +1,386 @@
# [122] 29075340: achievements
# cyanic's Quick and Easy Steamworks Achievements Integration for Ruby
# https://github.com/GMMan/RGSS_SteamUserStatsLite
# r4 06/16/16
#
# Drop steam_api.dll into the root of your project. Requires Steamworks SDK version >= 1.37.
#
# "Miller complained about how hard achievements were to implement in C++, so this was born."
#
$imported ||= {}
$imported['cyanic-SteamUserStatsLite'] = 4 # Slightly unorthodox, it's a version number.
# A context class to get Steamworks pointers to interfaces.
#
# @author cyanic
class SteamAPIContext
STEAMCLIENT_INTERFACE_VERSION = 'SteamClient017'
STEAMUSERSTATS_INTERFACE_VERSION = 'STEAMUSERSTATS_INTERFACE_VERSION011'
STEAMAPPS_INTERFACE_VERSION = 'STEAMAPPS_INTERFACE_VERSION008'
# Instantiates a new instance of +SteamAPIContext+.
def initialize
# @initted = false
# @h_steam_user = # @@dll_SteamAPI_GetHSteamUser.call
# if (@h_steam_pipe = # @@dll_SteamAPI_GetHSteamPipe.call) != 0
# return if (@steam_client = # @@dll_SteamInternal_CreateInterface.call(STEAMCLIENT_INTERFACE_VERSION)) == 0
# return if (@steam_user_stats = # @@dll_SteamAPI_ISteamClient_GetISteamUserStats.call(@steam_client, @h_steam_user, @h_steam_pipe, STEAMUSERSTATS_INTERFACE_VERSION)) == 0
# return if (@steam_apps = # @@dll_SteamAPI_ISteamClient_GetISteamApps.call(@steam_client, @h_steam_user, @h_steam_pipe, STEAMAPPS_INTERFACE_VERSION)) == 0
#
# @initted = true
# end
end
# Checks if context is initialized.
#
# @return [true, false] Whether context is initialized.
def initted?
@initted
end
# Gets the ISteamClient pointer
#
# @return [Fixnum, nil] The ISteamClient pointer if context is initialized, otherwise +nil+.
def steam_client
@steam_client if initted?
end
# Gets the ISteamUserStats pointer
#
# @return [Fixnum, nil] The ISteamUserStats pointer if context is initialized, otherwise +nil+.
def steam_user_stats
@steam_user_stats if initted?
end
# Gets the ISteamApps pointer
#
# @return [Fixnum, nil] The ISteamUserStats pointer if context is initialized, otherwise +nil+.
def steam_apps
@steam_apps if initted?
end
private
def self.is_64bit?
# Probably very bad detection of whether current runtime is 64-bit
(/x64/ =~ RUBY_PLATFORM) != nil
end
def self.steam_dll_name
# @@dll_name ||= self.is_64bit? ? 'steam_api64' : 'steam_api'
end
# @@dll_SteamAPI_GetHSteamUser = Win32API.new(self.steam_dll_name, 'SteamAPI_GetHSteamUser', '', 'I')
# @@dll_SteamAPI_GetHSteamPipe = Win32API.new(self.steam_dll_name, 'SteamAPI_GetHSteamPipe', '', 'I')
# @@dll_SteamInternal_CreateInterface = Win32API.new(self.steam_dll_name, 'SteamInternal_CreateInterface', 'P', 'I')
# @@dll_SteamAPI_ISteamClient_GetISteamUserStats = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamClient_GetISteamUserStats', 'IIIP', 'I')
# @@dll_SteamAPI_ISteamClient_GetISteamApps = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamClient_GetISteamApps', 'IIIP', 'I')
end
# A simple class for Steamworks UserStats integration.
#
# @author cyanic
class SteamUserStatsLite
# Instantiates a new instance of +SteamUserStatsLite+.
def initialize
@initted = false
api_initted = # @@dll_SteamAPI_Init.call % 256 != 0
if api_initted
@context = SteamAPIContext.new
if @context.initted?
@i_apps = @context.steam_apps
@i_user_stats = @context.steam_user_stats
@initted = true
end
end
end
# Shuts down Steamworks.
#
# @return [void]
def shutdown
if @initted
@i_apps = nil
@i_user_stats = nil
# @@dll_SteamAPI_Shutdown.call
@initted = false
end
end
# Checks if Steamworks is initialized.
#
# @return [true, false] Whether Steamworks is initialized.
def initted?
@initted
end
# Restarts the app if Steamworks is not availble.
#
# @param app_id [Integer] The app ID to relaunch as.
# @return [true, false] +true+ if current instance should exit, +false+ if not.
def self.restart_app_if_necessary(app_id)
# @@dll_SteamAPI_RestartAppIfNecessary.call(app_id) % 256 != 0
end
# Runs Steam callbacks.
#
# @return [void]
def update
# @@dll_SteamAPI_RunCallbacks.call if initted?
end
# Checks if current app is owned.
#
# @return [true, false, nil] Whether the current user has a license for the current app. +nil+ is returned if ownership status can't be retrieved.
def is_subscribed
if initted?
# @@dll_SteamAPI_ISteamApps_BIsSubscribed.call(@i_apps) % 256 != 0
else
nil
end
end
# Checks if a DLC is installed.
#
# @param app_id [Integer] The app ID of the DLC to check.
# @return [true, false, nil] Whether the DLC is installed. +nil+ is returned if the installation status can't be retrieved.
def is_dlc_installed(app_id)
if initted?
# @@dll_SteamAPI_ISteamApps_BIsDlcInstalled.call(@i_apps, app_id) % 256 != 0
else
nil
end
end
# Pulls current user's stats from Steam.
#
# @return [true, false] Whether the stats have been successfully pulled.
def request_current_stats
if initted?
# @@dll_SteamAPI_ISteamUserStats_RequestCurrentStats.call(@i_user_stats) % 256 != 0
else
false
end
end
# Gets the value of an INT stat.
#
# @param name [String] The name of the stat.
# @return [Integer, nil] The value of the stat, or +nil+ if the stat cannot be retrieved.
def get_stat_int(name)
if initted?
val = ' ' * 4
ok = # @@dll_SteamAPI_ISteamUserStats_GetStat.call(@i_user_stats, name, val) % 256 != 0
ok ? val.unpack('I')[0] : nil
else
nil
end
end
# Gets the value of an FLOAT stat.
#
# @param name [String] The name of the stat.
# @return [Float, nil] The value of the stat, or +nil+ if the stat cannot be retrieved.
def get_stat_float(name)
if initted?
val = ' ' * 4
ok = # @@dll_SteamAPI_ISteamUserStats_GetStat0.call(@i_user_stats, name, val) % 256 != 0
ok ? val.unpack('f')[0] : nil
else
nil
end
end
# Sets the value of a stat.
#
# @param name [String] The name of the stat.
# @param val [Integer, Float] The value of the stat.
# @return [true, false] Whether the stat was successfully updated.
# @example
# steam = SteamUserStatsLite.instance
# steam.set_stat 'YOUR_STAT_ID_HERE', 100
# steam.update
def set_stat(name, val)
if initted?
# @@dll_SteamAPI_ISteamUserStats_StoreStats.call(@i_user_stats) % 256 != 0 && ok
end
end
# Updates an AVGRATE stat.
#
# @param name [String] The name of the stat.
# @param count_this_session [Float] The value during this session.
# @param session_length [Float] The length of this session.
# @return [true, false] Whether the stat was successfully updated.
def update_avg_rate_stat(name, count_this_session, session_length)
if initted?
packed = self.class.pack_double session_length
end
end
# Gets an achievement's state.
#
# @param name [String] The name of the achievement.
# @return [true, false, nil] Whether the achievement has unlocked, or +nil+ if the achievement cannot be retrieved.
def get_achievement(name)
if initted?
val = ' '
ok = # @@dll_SteamAPI_ISteamUserStats_GetAchievement.call(@i_user_stats, name, val) % 256 != 0
ok ? val.unpack('C')[0] != 0 : nil
else
nil
end
end
# Sets an achievement as unlocked.
#
# @param name [String] The name of the achievement.
# @return [true, false] Whether the achievement was set successfully.
# @example
# steam = SteamUserStatsLite.instance
# steam.set_achievement 'YOUR_ACH_ID_HERE'
# steam.update
def set_achievement(name)
# if initted?
# ok = # @@dll_SteamAPI_ISteamUserStats_SetAchievement.call(@i_user_stats, name) % 256 != 0
# # @@dll_SteamAPI_ISteamUserStats_StoreStats.call(@i_user_stats) % 256 != 0 && ok
# # else
# # false
# end
end
# Sets an achievement as locked.
#
# @param name [String] The name of the achievement.
# @return [true, false] Whether the achievement was cleared successfully.
def clear_achievement(name)
# if initted?
# ok = # @@dll_SteamAPI_ISteamUserStats_ClearAchievement.call(@i_user_stats, name) % 256 != 0
# # @@dll_SteamAPI_ISteamUserStats_StoreStats.call(@i_user_stats) % 256 != 0 && ok
# else
# false
# end
end
# Gets an achievement's state and unlock time.
#
# @param name [String] The name of the achievement.
# @return [<Object, Time>] The achievement's state (+true+ or +false+) and the time it was unlocked.
def get_achievement_and_unlock_time(name)
if initted?
achieved = ' '
unlock_time = ' ' * 4
ok = # @@dll_SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime.call(@i_user_stats, name, achieved, unlock_time) % 256 != 0
ok ? [achieved.unpack('C')[0] != 0, Time.at(unlock_time.unpack('L')[0])] : nil
else
nil
end
end
# Gets the value of an achievement's display attribute.
#
# @param name [String] The name of the achievement.
# @param key [String] The key of the display attribute.
# @return [String] The value of the display attribute.
def get_achievement_display_attribute(name, key)
if initted?
# @@dll_SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute.call @i_user_stats, name, key
else
nil
end
end
# Gets the number of achievements.
#
# @return [Integer, nil] The number of achievements, or +nil+ if the number cannot be retrieved.
def get_num_achievements
if initted?
# @@dll_SteamAPI_ISteamUserStats_GetNumAchievements.call @i_user_stats
else
nil
end
end
# Gets the name of an achievement by its index.
#
# @param achievement [Integer] The index of the achievement.
# @return [String] The name of the achievement.
def get_achievement_name(achievement)
if initted?
# @@dll_SteamAPI_ISteamUserStats_GetAchievementName.call @i_user_stats, achievement
else
nil
end
end
# Resets all stats.
#
# @param achievements_too [true, false] Whether to reset achievements as well.
# @return [true, false] Whether achievements have been reset.
def reset_all_stats(achievements_too)
# if initted?
# ok = # @@dll_SteamAPI_ISteamUserStats_ResetAllStats.call(@i_user_stats, achievements_too ? 1 : 0) % 256 != 0
# # @@dll_SteamAPI_ISteamUserStats_StoreStats.call(@i_user_stats) % 256 != 0 && ok
# else
# false
# end
end
# Gets the global instance of SteamUserStatsLite.
#
# @return [SteamUserStatsLite] The global instance of the class.
def self.instance
@@instance
end
private
def self.is_64bit?
# Probably very bad detection of whether current runtime is 64-bit
(/x64/ =~ RUBY_PLATFORM) != nil
end
def self.steam_dll_name
# @@dll_name ||= self.is_64bit? ? 'steam_api64' : 'steam_api'
end
# Function imports
# @@dll_SteamAPI_RestartAppIfNecessary = Win32API.new(self.steam_dll_name, 'SteamAPI_RestartAppIfNecessary', 'I', 'I')
# @@dll_SteamAPI_Init = Win32API.new(self.steam_dll_name, 'SteamAPI_Init', '', 'I')
# @@dll_SteamAPI_Shutdown = Win32API.new(self.steam_dll_name, 'SteamAPI_Shutdown', '', 'V')
# @@dll_SteamAPI_RunCallbacks = Win32API.new(self.steam_dll_name, 'SteamAPI_RunCallbacks', '', 'V')
# @@dll_SteamAPI_ISteamUserStats_RequestCurrentStats = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_RequestCurrentStats', 'P', 'I')
# @@dll_SteamAPI_ISteamUserStats_GetStat = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetStat', 'PPP', 'I')
# @@dll_SteamAPI_ISteamUserStats_GetStat0 = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetStat0', 'PPP', 'I')
# @@dll_SteamAPI_ISteamUserStats_SetStat = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_SetStat', 'PPL', 'I')
# @@dll_SteamAPI_ISteamUserStats_SetStat0 = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_SetStat0', 'PPI', 'I')
# @@dll_SteamAPI_ISteamUserStats_UpdateAvgRateStat = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_UpdateAvgRateStat', 'PPIII', 'I')
# @@dll_SteamAPI_ISteamUserStats_GetAchievement = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetAchievement', 'PPP', 'I')
# @@dll_SteamAPI_ISteamUserStats_SetAchievement = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_SetAchievement', 'PP', 'I')
# @@dll_SteamAPI_ISteamUserStats_ClearAchievement = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_ClearAchievement', 'PP', 'I')
# @@dll_SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime', 'PPPP', 'I')
# @@dll_SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute', 'PPP', 'P')
# @@dll_SteamAPI_ISteamUserStats_GetNumAchievements = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetNumAchievements', 'P', 'I')
# @@dll_SteamAPI_ISteamUserStats_GetAchievementName = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_GetAchievementName', 'PI', 'P')
# @@dll_SteamAPI_ISteamUserStats_StoreStats = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_StoreStats', 'P', 'I')
# @@dll_SteamAPI_ISteamUserStats_ResetAllStats = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamUserStats_ResetAllStats', 'PI', 'I')
# @@dll_SteamAPI_ISteamApps_BIsSubscribed = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamApps_BIsSubscribed', 'P', 'I')
# @@dll_SteamAPI_ISteamApps_BIsDlcInstalled = Win32API.new(self.steam_dll_name, 'SteamAPI_ISteamApps_BIsDlcInstalled', 'PI', 'I')
@@instance = self.new
def self.pack_float(val)
# Packs number to a string, then unpack to an int
inter = [val].pack 'e'
inter.unpack('I')[0]
end
def self.pack_double(val)
# Packs number to a string, then unpack to an array of two ints
inter = [val].pack 'd'
inter.unpack 'II'
end
end

View file

@ -0,0 +1,613 @@
#==============================================================================
# ■ Window_Base
#------------------------------------------------------------------------------
#  ゲーム中の全てのウィンドウのスーパークラスです。
#==============================================================================
class Window_Base < Window
#--------------------------------------------------------------------------
# ● オブジェクト初期化
#--------------------------------------------------------------------------
def initialize(x, y, width, height)
super
self.windowskin = Cache.system("Window")
update_padding
update_tone
create_contents
@opening = @closing = false
end
#--------------------------------------------------------------------------
# ● 解放
#--------------------------------------------------------------------------
def dispose
contents.dispose unless disposed?
super
end
#--------------------------------------------------------------------------
# ● 行の高さを取得
#--------------------------------------------------------------------------
def line_height
return 24
end
#--------------------------------------------------------------------------
# ● 標準パディングサイズの取得
#--------------------------------------------------------------------------
def standard_padding
return 12
end
#--------------------------------------------------------------------------
# ● パディングの更新
#--------------------------------------------------------------------------
def update_padding
self.padding = standard_padding
end
#--------------------------------------------------------------------------
# ● ウィンドウ内容の幅を計算
#--------------------------------------------------------------------------
def contents_width
width - standard_padding * 2
end
#--------------------------------------------------------------------------
# ● ウィンドウ内容の高さを計算
#--------------------------------------------------------------------------
def contents_height
height - standard_padding * 2
end
#--------------------------------------------------------------------------
# ● 指定行数に適合するウィンドウの高さを計算
#--------------------------------------------------------------------------
def fitting_height(line_number)
line_number * line_height + standard_padding * 2
end
#--------------------------------------------------------------------------
# ● 色調の更新
#--------------------------------------------------------------------------
def update_tone
self.tone.set($game_system.window_tone)
end
#--------------------------------------------------------------------------
# ● ウィンドウ内容の作成
#--------------------------------------------------------------------------
def create_contents
test5 = contents_width
testf = contents_height
# Debugging: Log the current types and values of contents_width and contents_height
puts "Before disposing, contents width: #{contents_width}, height: #{contents_height}"
puts "Before disposing, contents width type: #{contents_width.class}, height type: #{contents_height.class}"
contents.dispose
# Check for nil and ensure that contents_width and contents_height are integers
if contents_width.nil? || contents_height.nil?
puts "sadada disposing, contents width: #{contents_width}, height: #{contents_height}"
puts "Warning: contents_width or contents_height is nil, setting default values."
contents_width = 190 # Default width (you can adjust this to your needs)
contents_height = 120 # Default height (you can adjust this to your needs)
end
# Ensure contents_width and contents_height are valid integers before proceeding
# if contents_width.is_a?(Integer) && contents_height.is_a?(Integer)
if contents_width > 0 && contents_height > 0
# Limit height to a maximum of 2048 pixels
# contents_height = 2048 if contents_height > 2048
self.contents = Bitmap.new(contents_width, contents_height)
puts "Created new bitmap with dimensions: #{contents_width} x #{contents_height}"
else
puts "Invalid dimensions: creating fallback 1x1 bitmap"
self.contents = Bitmap.new(1, 1)
end
# else
# # If contents_width or contents_height are not valid integers, create a fallback 1x1 bitmap
# puts "Warning: Invalid types for contents width or height. Creating fallback 1x1 bitmap."
# self.contents = Bitmap.new(1, 1)
# end
end
def create_contents
# puts "vv Before disposing, contents width: #{contents_width}, height: #{contents_height}"
# puts "vv Before disposing, contents width type: #{contents_width.class}, height type: #{contents_height.class}"
contents.dispose
# puts "Before disposing, contents width: #{contents_width}, height: #{contents_height}"
# puts "Before disposing, contents width type: #{contents_width.class}, height type: #{contents_height.class}"
# end
if contents_height > 10000
self.contents = Bitmap.new(1, 1)
elsif contents_width > 0 && contents_height > 0
self.contents = Bitmap.new(contents_width, contents_height)
puts "Created new bitmap with dimensions: #{contents_width} x #{contents_height}"
else
self.contents = Bitmap.new(1, 1)
end
end
#--------------------------------------------------------------------------
# ● フレーム更新
#--------------------------------------------------------------------------
def update
super
update_tone
update_open if @opening
update_close if @closing
end
#--------------------------------------------------------------------------
# ● 開く処理の更新
#--------------------------------------------------------------------------
def update_open
self.openness += 48
@opening = false if open?
end
#--------------------------------------------------------------------------
# ● 閉じる処理の更新
#--------------------------------------------------------------------------
def update_close
self.openness -= 48
@closing = false if close?
end
#--------------------------------------------------------------------------
# ● ウィンドウを開く
#--------------------------------------------------------------------------
def open
@opening = true unless open?
@closing = false
self
end
#--------------------------------------------------------------------------
# ● ウィンドウを閉じる
#--------------------------------------------------------------------------
def close
@closing = true unless close?
@opening = false
self
end
#--------------------------------------------------------------------------
# ● ウィンドウの表示
#--------------------------------------------------------------------------
def show
self.visible = true
self
end
#--------------------------------------------------------------------------
# ● ウィンドウの非表示
#--------------------------------------------------------------------------
def hide
self.visible = false
self
end
#--------------------------------------------------------------------------
# ● ウィンドウのアクティブ化
#--------------------------------------------------------------------------
def activate
self.active = true
self
end
#--------------------------------------------------------------------------
# ● ウィンドウの非アクティブ化
#--------------------------------------------------------------------------
def deactivate
self.active = false
self
end
#--------------------------------------------------------------------------
# ● 文字色取得
# n : 文字色番号(0..31)
#--------------------------------------------------------------------------
def text_color(n)
windowskin.get_pixel(64 + (n % 8) * 8, 96 + (n / 8) * 8)
end
#--------------------------------------------------------------------------
# ● 各種文字色の取得
#--------------------------------------------------------------------------
def normal_color; text_color(0); end; # 通常
def system_color; text_color(16); end; # システム
def crisis_color; text_color(17); end; # ピンチ
def knockout_color; text_color(18); end; # 戦闘不能
def gauge_back_color; text_color(19); end; # ゲージ背景
def hp_gauge_color1; text_color(20); end; # HP ゲージ 1
def hp_gauge_color2; text_color(21); end; # HP ゲージ 2
def mp_gauge_color1; text_color(22); end; # MP ゲージ 1
def mp_gauge_color2; text_color(23); end; # MP ゲージ 2
def mp_cost_color; text_color(23); end; # 消費 TP
def power_up_color; text_color(24); end; # 装備 パワーアップ
def power_down_color; text_color(25); end; # 装備 パワーダウン
def tp_gauge_color1; text_color(28); end; # TP ゲージ 1
def tp_gauge_color2; text_color(29); end; # TP ゲージ 2
def tp_cost_color; text_color(29); end; # 消費 TP
#--------------------------------------------------------------------------
# ● 保留項目の背景色を取得
#--------------------------------------------------------------------------
def pending_color
windowskin.get_pixel(80, 80)
end
#--------------------------------------------------------------------------
# ● 半透明描画用のアルファ値を取得
#--------------------------------------------------------------------------
def translucent_alpha
return 160
end
#--------------------------------------------------------------------------
# ● テキスト描画色の変更
# enabled : 有効フラグ。false のとき半透明で描画
#--------------------------------------------------------------------------
def change_color(color, enabled = true)
contents.font.color.set(color)
contents.font.color.alpha = translucent_alpha unless enabled
end
#--------------------------------------------------------------------------
# ● テキストの描画
# args : Bitmap#draw_text と同じ
#--------------------------------------------------------------------------
def draw_text(*args)
contents.draw_text(*args)
end
#--------------------------------------------------------------------------
# ● テキストサイズの取得
#--------------------------------------------------------------------------
def text_size(str)
contents.text_size(str)
end
#--------------------------------------------------------------------------
# ● 制御文字つきテキストの描画
#--------------------------------------------------------------------------
def draw_text_ex(x, y, text)
reset_font_settings
text = convert_escape_characters(text)
pos = {:x => x, :y => y, :new_x => x, :height => calc_line_height(text)}
process_character(text.slice!(0, 1), text, pos) until text.empty?
end
#--------------------------------------------------------------------------
# ● フォント設定のリセット
#--------------------------------------------------------------------------
def reset_font_settings
change_color(normal_color)
contents.font.size = Font.default_size
contents.font.bold = false
contents.font.italic = false
end
#--------------------------------------------------------------------------
# ● 制御文字の事前変換
# 実際の描画を始める前に、原則として文字列に変わるものだけを置き換える。
# 文字「\」はエスケープ文字(\e)に変換。
#--------------------------------------------------------------------------
def convert_escape_characters(text)
result = text.to_s.clone
result.gsub!(/\\/) { "\e" }
result.gsub!(/\e\e/) { "\\" }
result.gsub!(/\eV\[(\d+)\]/i) { $game_variables[$1.to_i] }
result.gsub!(/\eV\[(\d+)\]/i) { $game_variables[$1.to_i] }
result.gsub!(/\eN\[(\d+)\]/i) { actor_name($1.to_i) }
result.gsub!(/\eP\[(\d+)\]/i) { party_member_name($1.to_i) }
result.gsub!(/\eG/i) { Vocab::currency_unit }
result
end
#--------------------------------------------------------------------------
# ● アクター n 番の名前を取得
#--------------------------------------------------------------------------
def actor_name(n)
actor = n >= 1 ? $game_actors[n] : nil
actor ? actor.name : ""
end
#--------------------------------------------------------------------------
# ● パーティメンバー n 番の名前を取得
#--------------------------------------------------------------------------
def party_member_name(n)
actor = n >= 1 ? $game_party.members[n - 1] : nil
actor ? actor.name : ""
end
#--------------------------------------------------------------------------
# ● 文字の処理
# c : 文字
# text : 描画処理中の文字列バッファ(必要なら破壊的に変更)
# pos : 描画位置 {:x, :y, :new_x, :height}
#--------------------------------------------------------------------------
def process_character(c, text, pos)
case c
when "\n" # 改行
process_new_line(text, pos)
when "\f" # 改ページ
process_new_page(text, pos)
when "\e" # 制御文字
process_escape_character(obtain_escape_code(text), text, pos)
else # 普通の文字
process_normal_character(c, pos)
end
end
#--------------------------------------------------------------------------
# ● 通常文字の処理
#--------------------------------------------------------------------------
def process_normal_character(c, pos)
text_width = text_size(c).width
draw_text(pos[:x], pos[:y], text_width * 2, pos[:height], c)
pos[:x] += text_width
end
#--------------------------------------------------------------------------
# ● 改行文字の処理
#--------------------------------------------------------------------------
def process_new_line(text, pos)
pos[:x] = pos[:new_x]
pos[:y] += pos[:height]
pos[:height] = calc_line_height(text)
end
#--------------------------------------------------------------------------
# ● 改ページ文字の処理
#--------------------------------------------------------------------------
def process_new_page(text, pos)
end
#--------------------------------------------------------------------------
# ● 制御文字の本体を破壊的に取得
#--------------------------------------------------------------------------
def obtain_escape_code(text)
text.slice!(/^[\$\.\|\^!><\{\}\\]|^[A-Z]+/i)
end
#--------------------------------------------------------------------------
# ● 制御文字の引数を破壊的に取得
#--------------------------------------------------------------------------
def obtain_escape_param(text)
text.slice!(/^\[\d+\]/)[/\d+/].to_i rescue 0
end
#--------------------------------------------------------------------------
# ● 制御文字の処理
# code : 制御文字の本体部分(「\C[1]」なら「C」)
#--------------------------------------------------------------------------
def process_escape_character(code, text, pos)
case code.upcase
when 'C'
change_color(text_color(obtain_escape_param(text)))
when 'I'
process_draw_icon(obtain_escape_param(text), pos)
when '{'
make_font_bigger
when '}'
make_font_smaller
end
end
#--------------------------------------------------------------------------
# ● 制御文字によるアイコン描画の処理
#--------------------------------------------------------------------------
def process_draw_icon(icon_index, pos)
draw_icon(icon_index, pos[:x], pos[:y])
pos[:x] += 24
end
#--------------------------------------------------------------------------
# ● フォントを大きくする
#--------------------------------------------------------------------------
def make_font_bigger
contents.font.size += 8 if contents.font.size <= 64
end
#--------------------------------------------------------------------------
# ● フォントを小さくする
#--------------------------------------------------------------------------
def make_font_smaller
contents.font.size -= 8 if contents.font.size >= 16
end
#--------------------------------------------------------------------------
# ● 行の高さを計算
# restore_font_size : 計算後にフォントサイズを元に戻す
#--------------------------------------------------------------------------
def calc_line_height(text, restore_font_size = true)
result = [line_height, contents.font.size].max
last_font_size = contents.font.size
text.slice(/^.*$/).scan(/\e[\{\}]/).each do |esc|
make_font_bigger if esc == "\e{"
make_font_smaller if esc == "\e}"
result = [result, contents.font.size].max
end
contents.font.size = last_font_size if restore_font_size
result
end
#--------------------------------------------------------------------------
# ● ゲージの描画
# rate : 割合(1.0 で満タン)
# color1 : グラデーション 左端
# color2 : グラデーション 右端
#--------------------------------------------------------------------------
def draw_gauge(x, y, width, rate, color1, color2)
fill_w = (width * rate).to_i
gauge_y = y + line_height - 8
contents.fill_rect(x, gauge_y, width, 6, gauge_back_color)
contents.gradient_fill_rect(x, gauge_y, fill_w, 6, color1, color2)
end
#--------------------------------------------------------------------------
# ● アイコンの描画
# enabled : 有効フラグ。false のとき半透明で描画
#--------------------------------------------------------------------------
def draw_icon(icon_index, x, y, enabled = true)
bitmap = Cache.system("Iconset")
rect = Rect.new(icon_index % 16 * 24, icon_index / 16 * 24, 24, 24)
contents.blt(x, y, bitmap, rect, enabled ? 255 : translucent_alpha)
end
#--------------------------------------------------------------------------
# ● 顔グラフィックの描画
# enabled : 有効フラグ。false のとき半透明で描画
#--------------------------------------------------------------------------
def draw_face(face_name, face_index, x, y, enabled = true)
bitmap = Cache.face(face_name)
rect = Rect.new(face_index % 4 * 96, face_index / 4 * 96, 96, 96)
contents.blt(x, y, bitmap, rect, enabled ? 255 : translucent_alpha)
bitmap.dispose
end
#--------------------------------------------------------------------------
# ● 歩行グラフィックの描画
#--------------------------------------------------------------------------
def draw_character(character_name, character_index, x, y)
return unless character_name
bitmap = Cache.character(character_name)
sign = character_name[/^[\!\$]./]
if sign && sign.include?('$')
cw = bitmap.width / 3
ch = bitmap.height / 4
else
cw = bitmap.width / 12
ch = bitmap.height / 8
end
n = character_index
src_rect = Rect.new((n%4*3+1)*cw, (n/4*4)*ch, cw, ch)
contents.blt(x - cw / 2, y - ch, bitmap, src_rect)
end
#--------------------------------------------------------------------------
# ● HP の文字色を取得
#--------------------------------------------------------------------------
def hp_color(actor)
return knockout_color if actor.hp == 0
return crisis_color if actor.hp < actor.mhp / 4
return normal_color
end
#--------------------------------------------------------------------------
# ● MP の文字色を取得
#--------------------------------------------------------------------------
def mp_color(actor)
return crisis_color if actor.mp < actor.mmp / 4
return normal_color
end
#--------------------------------------------------------------------------
# ● TP の文字色を取得
#--------------------------------------------------------------------------
def tp_color(actor)
return normal_color
end
#--------------------------------------------------------------------------
# ● アクターの歩行グラフィック描画
#--------------------------------------------------------------------------
def draw_actor_graphic(actor, x, y)
draw_character(actor.character_name, actor.character_index, x, y)
end
#--------------------------------------------------------------------------
# ● アクターの顔グラフィック描画
#--------------------------------------------------------------------------
def draw_actor_face(actor, x, y, enabled = true)
draw_face(actor.face_name, actor.face_index, x, y, enabled)
end
#--------------------------------------------------------------------------
# ● 名前の描画
#--------------------------------------------------------------------------
def draw_actor_name(actor, x, y, width = 112)
change_color(hp_color(actor))
draw_text(x, y, width, line_height, actor.name)
end
#--------------------------------------------------------------------------
# ● 職業の描画
#--------------------------------------------------------------------------
def draw_actor_class(actor, x, y, width = 112)
change_color(normal_color)
draw_text(x, y, width, line_height, actor.class.name)
end
#--------------------------------------------------------------------------
# ● 二つ名の描画
#--------------------------------------------------------------------------
def draw_actor_nickname(actor, x, y, width = 180)
change_color(normal_color)
draw_text(x, y, width, line_height, actor.nickname)
end
#--------------------------------------------------------------------------
# ● レベルの描画
#--------------------------------------------------------------------------
def draw_actor_level(actor, x, y)
change_color(system_color)
draw_text(x, y, 32, line_height, Vocab::level_a)
change_color(normal_color)
draw_text(x + 32, y, 24, line_height, actor.level, 2)
end
#--------------------------------------------------------------------------
# ● ステートおよび強化/弱体のアイコンを描画
#--------------------------------------------------------------------------
def draw_actor_icons(actor, x, y, width = 96)
icons = (actor.state_icons + actor.buff_icons)[0, width / 24]
icons.each_with_index {|n, i| draw_icon(n, x + 24 * i, y) }
end
#--------------------------------------------------------------------------
# ● 現在値/最大値を分数形式で描画
# current : 現在値
# max : 最大値
# color1 : 現在値の色
# color2 : 最大値の色
#--------------------------------------------------------------------------
def draw_current_and_max_values(x, y, width, current, max, color1, color2)
change_color(color1)
xr = x + width
if width < 96
draw_text(xr - 40, y, 42, line_height, current, 2)
else
draw_text(xr - 92, y, 42, line_height, current, 2)
change_color(color2)
draw_text(xr - 52, y, 12, line_height, "/", 2)
draw_text(xr - 42, y, 42, line_height, max, 2)
end
end
#--------------------------------------------------------------------------
# ● HP の描画
#--------------------------------------------------------------------------
def draw_actor_hp(actor, x, y, width = 124)
draw_gauge(x, y, width, actor.hp_rate, hp_gauge_color1, hp_gauge_color2)
change_color(system_color)
draw_text(x, y, 30, line_height, Vocab::hp_a)
draw_current_and_max_values(x, y, width, actor.hp, actor.mhp,
hp_color(actor), normal_color)
end
#--------------------------------------------------------------------------
# ● MP の描画
#--------------------------------------------------------------------------
def draw_actor_mp(actor, x, y, width = 124)
draw_gauge(x, y, width, actor.mp_rate, mp_gauge_color1, mp_gauge_color2)
change_color(system_color)
draw_text(x, y, 30, line_height, Vocab::mp_a)
draw_current_and_max_values(x, y, width, actor.mp, actor.mmp,
mp_color(actor), normal_color)
end
#--------------------------------------------------------------------------
# ● TP の描画
#--------------------------------------------------------------------------
def draw_actor_tp(actor, x, y, width = 124)
draw_gauge(x, y, width, actor.tp_rate, tp_gauge_color1, tp_gauge_color2)
change_color(system_color)
draw_text(x, y, 30, line_height, Vocab::tp_a)
change_color(tp_color(actor))
draw_text(x + width - 42, y, 42, line_height, actor.tp.to_i, 2)
end
#--------------------------------------------------------------------------
# ● シンプルなステータスの描画
#--------------------------------------------------------------------------
def draw_actor_simple_status(actor, x, y)
draw_actor_name(actor, x, y)
draw_actor_level(actor, x, y + line_height * 1)
draw_actor_icons(actor, x, y + line_height * 2)
draw_actor_class(actor, x + 120, y)
draw_actor_hp(actor, x + 120, y + line_height * 1)
draw_actor_mp(actor, x + 120, y + line_height * 2)
end
#--------------------------------------------------------------------------
# ● 能力値の描画
#--------------------------------------------------------------------------
def draw_actor_param(actor, x, y, param_id)
change_color(system_color)
draw_text(x, y, 120, line_height, Vocab::param(param_id))
change_color(normal_color)
draw_text(x + 120, y, 36, line_height, actor.param(param_id), 2)
end
#--------------------------------------------------------------------------
# ● アイテム名の描画
# enabled : 有効フラグ。false のとき半透明で描画
#--------------------------------------------------------------------------
def draw_item_name(item, x, y, enabled = true, width = 172)
return unless item
draw_icon(item.icon_index, x, y, enabled)
change_color(normal_color, enabled)
draw_text(x + 24, y, width, line_height, item.name)
end
#--------------------------------------------------------------------------
# ● 通貨単位つき数値(所持金など)の描画
#--------------------------------------------------------------------------
def draw_currency_value(value, unit, x, y, width)
cx = text_size(unit).width
change_color(normal_color)
draw_text(x, y, width - cx - 2, line_height, value, 2)
change_color(system_color)
draw_text(x, y, width, line_height, unit, 2)
end
#--------------------------------------------------------------------------
# ● 能力値変化の描画色取得
#--------------------------------------------------------------------------
def param_change_color(change)
return power_up_color if change > 0
return power_down_color if change < 0
return normal_color
end
end

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,191 @@
# ===========================================================================
# ★ WF-RGSS Scripts ★
# Exit-EX 終了処理スクリプト(共通実行スクリプト VXAce版)
# バージョン : rev-2.1 (2012-1-24)
# 作者 : A Crying Minister (WHITE-FLUTE)
# サポート先URI: http://www.whiteflute.org/wfrgss/
# ---------------------------------------------------------------------------
# 機能:
# ・VXAceの終了処理をXP/VX形式(SystemExit送出)に設定します。
# ・VXAceに限り、共通実行スクリプトと同様に使えます。
# ---------------------------------------------------------------------------
# 設置場所 :Mainセクション(一番最後)に上書き
# または、Mainセクションの直前
# 必要スクリプト:
# ・共通スクリプト
# 必要DLL
# ・WFExit.dll
# 注意事項:
# ▽ 共通スクリプトが必要です。
# 改造して使用することを推奨しますが、そのまま使ってもOKです。
# ▽ デバッグモードでエラーを記録する場合、
# 現在のユーザで書き込みを行えることが必要になります。
# ▽ スクリプトが実行されます。これ以降のセクションは実行されません。
#==============================================================================
# ◆ Main ( Execute )
#------------------------------------------------------------------------------
#  各クラスの定義が終わった後、ここから実際の処理が始まります。
#==============================================================================
#module Exit
# # ---------------------------------------------------------------------------
# # ◆ カスタマイズポイント セットアップ処理を記述します。
# # ---------------------------------------------------------------------------
# def self.setup
# end
# # ---------------------------------------------------------------------------
# # ◆ カスタマイズポイント 解放処理を記述します。
# # ---------------------------------------------------------------------------
# def self.dispose
# DataManager.save_system
# end
#end
# ---------------------------------------------------------------------------
# ◆ 以下の内容は変更する必要はありません。
# ---------------------------------------------------------------------------
#==============================================================================
# ◆ Exit モジュール
#------------------------------------------------------------------------------
def hook_exit()
# Simulate what the hookExit function might do
# puts "Simulated hookExit called with parameter:"
# You can add more simulated behavior here, like logging, altering global variables, etc.
end
module Exit
# ---------------------------------------------------------------------------
# ◆ 処理実行
# ---------------------------------------------------------------------------
begin
@@hook = method(:hook_exit)
@@exit = method(:hook_exit)
@@quit = method(:hook_exit)
@@reset = method(:hook_exit)
@@clear = method(:hook_exit)
#@@hook = Win32API.new('System/WFExit','hookExit','v','l')
#@@exit = Win32API.new('System/WFExit','getToExit','v','l')
#@@quit = Win32API.new('System/WFExit','Quit','v','v')
#@@reset = Win32API.new('System/WFExit','getToReset','v','l')
#@@clear = Win32API.new('System/WFExit','clearReset','v','v')
rescue Exception
raise if $TEST
raise( LoadError , "cannot read modules.(WFExit.dll)")
end
@@hook.call()
# ---------------------------------------------------------------------------
# ◆ 終了を監視する
# ---------------------------------------------------------------------------
def self.toexit
raise SystemExit.new(0) if @@exit.call() == 1
end
# ---------------------------------------------------------------------------
# ◆ F12リセットを監視する
# ---------------------------------------------------------------------------
def self.toreset
raise RGSSReset if @duration && @duration <= 0 if @@reset.call() == 1
if @duration && @duration > 0
@duration -= 1
@@clear.call()
end
end
# ---------------------------------------------------------------------------
# ◆ 本当の終了処理
# ---------------------------------------------------------------------------
def self.quit
@@quit.call()
end
# ---------------------------------------------------------------------------
# ◆ リセットカウントをクリア
# ---------------------------------------------------------------------------
def self.clearreset(wait = false)
@@clear.call()
@duration = wait ? 120 : 0
end
end
# ---------------------------------------------------------------------------
# ◆ 終了監視をセット
# ---------------------------------------------------------------------------
class << Graphics
alias __wfexit_uodate__ update
def Graphics.update
__wfexit_uodate__
Exit.toexit
Exit.toreset
end
end
# ---------------------------------------------------------------------------
# ◆ セットアップをセット
# ---------------------------------------------------------------------------
class << SceneManager
alias __wfexit_run__ run
#--------------------------------------------------------------------------
# ● 実行
#--------------------------------------------------------------------------
def SceneManager.run
Exit.setup
Exit.clearreset(true)
__wfexit_run__
end
end
# ---------------------------------------------------------------------------
# ◆ 処理実行
# ---------------------------------------------------------------------------
begin
rgss_main { SceneManager.run }
# 以下、例外処理
rescue BugDetected, InternalBugDetected => errobj
begin
MessageBox.fatalerror( errobj )
raise SystemExit.new(1)
rescue Hangup
nil
end
rescue SyntaxError => errobj
# -------------------------------------------------------------------------
# ◆ 例外 SyntaxError
# -------------------------------------------------------------------------
# この例外はバグかセットアップが適切にされていない状況で無い限り、
# 補足されることはない
begin
raise( BugDetected,
"[FATAL] The invalidated exception was detected. \n\n" +
"Exception:\n#{errobj}")
rescue BugDetected => errobj
begin
MessageBox.fatalerror( errobj )
raise SystemExit.new(1)
rescue Hangup
nil
end
end
rescue SystemExit
# 終了を補足する。エラーメッセージに書き加えない。
rescue Exception => errobj
# -------------------------------------------------------------------------
# ◆ 例外処理
# 特に指定されていない例外を補足します。
# ※ rev-2 より、Errno::ENOENT もここで補足します。
# -------------------------------------------------------------------------
begin
MessageBox.fatalerror( errobj )
raise SystemExit.new(1)
rescue Hangup
nil
end
ensure
# -------------------------------------------------------------------------
# ● 後処理
# -------------------------------------------------------------------------
# 後処理を担当します。
# スクリプト内容によってはここで解放処理が必要になることがあります。
Exit.dispose
# ★ ----------------------------------------------------------------------
Exit.quit # フックを解放する。実行しないと終了しない危険性大
end
exit # Mainセクションが後に控えている時に処理が渡らないようにする

View file

@ -0,0 +1,602 @@
# Kawariki MKXP preload infrastructure
module Preload
# Kawariki mkxp resources location
Path = File.dirname __FILE__
# Require common libs
def self.require(name)
Kernel.require File.join(Path, "libs", name)
end
module Common
# In RMXP mode, Kernel.print opens message boxes
def print(text)
STDOUT.puts("[preload] " + text.to_s)
end
end
extend Common
# -------------------------------------------------------------------------
# Patches
# -------------------------------------------------------------------------
class Context
include Common
def initialize(scripts)
@scripts = scripts
@script_instances = {}
@options = {}
@blacklist = []
@delay = {}
@script_id_digits = Math.log10(scripts.size).ceil
end
attr_reader :script_id_digits, :script_loc_format
# Scripts
def script(i)
@script_instances[i] ||= Script.new self, i, @scripts[i]
end
def script_count
@scripts.size
end
def each_script
(0...@scripts.size).each {|i| yield script i}
end
def last_script
script (script_count - 1)
end
def script_loc(scriptid)
return script(scriptid).loc
end
def blacklisted?(script)
@blacklist.include? script.name
end
def add_script(name, code)
@scripts.pop
@scripts.push [name, "", nil, code]
# TODO: Find an empty script to canibalize instead
end
# Read options from environment
FalseEnvValues = [nil, "", "0", "no"]
def read_env(env=ENV)
env_bool = ->(name) {!FalseEnvValues.include?(env[name])}
env_str = ->(name) {e = env[name]; e unless e == ""}
env_list = ->(name, delim) {e = env[name]; e.nil? ? [] : e.split(delim)}
set :dump_scripts_raw, env_str.("KAWARIKI_MKXP_DUMP_SCRIPTS")
set :dump_scripts_patched, env_str.("KAWARIKI_MKXP_DUMP_PATCHED_SCRIPTS")
mark :dont_run_game if env_bool.("KAWARIKI_MKXP_DRY_RUN")
mark :no_font_effects if env_bool.("KAWARIKI_MKXP_NO_FONT_EFFECTS")
@blacklist = env_list.("KAWARIKI_MKXP_FILTER_SCRIPTS", ",")
end
def read_system(system=System)
# TODO: Non mkxp-z variants
set :mkxp_version, system::VERSION
set :mkxp_version_tuple, (system::VERSION.split ".").map{|d| d.to_i}
if (self[:mkxp_version_tuple] <=> [2, 4]) >= 0 then
mark :mkxpz_24
_config = CFG
else
_config = system::CONFIG
end
# set :rgss_version, _config["rgssVersion"].to_i
# puts "jj"+_config["gameFolder"].to_s
# Preload.require "PreloadIni.rb"
# puts "vvv"+ENV["rpgvers"]
set :rgss_version, ENV["rpgvers"].to_i
# puts self[:zeusrpgver]
# puts vcode
if defined?(RGSS_VERSION) && RGSS_VERSION == "3.0.1" then
# See mkxp-z/mri-binding.cpp
# puts "vvv"+RGSS_VERSION
set :rgss_version, 3
end
# FIXME: can this be reliably retrieved from MKXP if set to 0 in config?
if self[:rgss_version] == 0 then
print "Warning: rgssVersion not set in MKXP config. Are you running mkxp directly?"
if RGSS_VERSION == "3.0.1" then
# See mkxp-z/mri-binding.cpp
set :rgss_version, 3
else
print "Warning: Cannot guess RGSS version. Kawariki should automatically set it correctly."
end
end
if self[:mkxp_version] == "MKXPZ_VERSION" then
print "Note: Using mkxp-z with broken System::VERSION reporting. Cannot detect real mkxp-z version"
set :mkxp_version, "mkxp-z"
end
end
# Options
def set(sym, value=true)
@options.store sym, value unless value.nil?
end
def [](sym)
@options[sym]
end
def mark(*flags)
flags.each{|flag| set flag, true}
end
def flag?(flag)
@options.key? flag
end
# Delay
DelaySlots = [:after_patches]
def delay(slot, &p)
raise "Unknown delay slot #{slot}" unless DelaySlots.include? slot
@delay[slot] = [] unless @delay.key? slot
@delay[slot].push p
end
def run_delay_slot(slot, *args)
raise "Unknown delay slot #{slot}" unless DelaySlots.include? slot
if @delay[slot] then
@delay[slot].each {|p| p.call(self, *args)}
@delay.delete slot
end
end
end
class Script
def initialize(context, i, script)
@context = context
@index = i
@script = script
@log = []
end
attr_reader :context
attr_reader :index
def log(msg=nil)
@log.push msg unless msg.nil? || @log.last == msg
@log
end
def loc
"##{index.to_s.rjust @context.script_id_digits} '#{name}'"
end
def [](i)
@script[i]
end
def name
@script[1]
end
def source
@script[3]
end
def sub!(*p)
@script[3].gsub!(*p)
end
def source=(code)
@script[3] = code
end
def load_file(path)
log "replaced with #{File.basename path}"
@script[3] = File.read(path)
end
def remove
log "removed"
@script[3] = ""
end
# Extract $imported key only once
# $imported['Hello'] = 1
# $imported[:Hello] = true
# ($imported ||= {})["Hello"] = true
# Type (String/Symbol) is preserved
ImportedKeyExpr = /^\s*(?:\$imported|\(\s*\$imported(?:\s*\|\|=\s*\{\s*\})?\s*\))\[(:\w+|'[^']+'|"[^"]+")\]\s*=\s*(.+)\s*$/
def _extract_imported
match = ImportedKeyExpr.match(source)
@imported_entry = !match.nil?
return unless @imported_entry
@imported_key = match[1][0] == ':' ? match[1][1..].to_sym : match[1][1...-1]
@imported_value = match[2]
end
def imported_entry?
_extract_imported if @imported_entry.nil?
@imported_entry
end
def imported_key
_extract_imported if @imported_entry.nil?
@imported_key
end
def imported_value
_extract_imported if @imported_entry.nil?
@imported_value
end
end
class Patch
include Common
def initialize(desc=nil)
@description = desc
@conditions = []
@actions = []
@terminal = false
end
def is_applicable(script)
return @conditions.all? {|cond| cond.call(script)}
end
def apply(script)
print "Patch #{script.loc}: #{@description}"
@actions.each {|action| action.call script}
@terminal
end
def eval(script)
apply script if is_applicable script
end
# --- Conditions ---
# Arbitrary condition
def if?(&p)
@conditions.push p
self
end
# Source code contains text
def include?(str)
# XXX: maybe should restrict this to the start of the script for performance?
if? {|script| script.source.include? str}
end
# Source code matches (any) pattern
def match?(*ps)
pattern = Regexp.union(*ps)
if? {|script| script.source.match? pattern}
end
# Script sets $imported[key]
def imported?(key)
if? {|script| script.imported_key == key}
end
# Global flag set
def flag?(flag)
if? {|script| script.context.flag? flag}
end
# --- Actions ---
# Arbitrary action
def then!(&p)
@actions.push p
self
end
# Run arbitrary action later
def delay!(slot, &p)
@actions.push proc{|script|script.context.delay(slot, &p)}
self
end
# Substitute text
def sub!(pattern, replacement)
@actions.push proc{|script| script.source.gsub! pattern, replacement}
self
end
# Set a global flag for later reference
def flag!(*flags)
@actions.push proc{|script| script.context.mark *flags}
self
end
# Remove the script (terminal)
def remove!
@actions.push proc{|script| script.remove }
@terminal = true
self
end
# Replace the whole script with a file from ports/ (terminal)
def replace!(filename)
puts filename
@actions.push proc{|script| script.load_file File.join(Path, "ports", filename)}
@terminal = true
self
end
# Stop processing this script if patch is applicable (terminal)
def next!
@terminal = true
self
end
end
# -------------------------------------------------------------------------
# Apply Patches
# -------------------------------------------------------------------------
class ClassInfo
include Common
def initialize(name, script, supername)
@name = name
@defs = [[script, supername]]
@superdef = 0
end
attr_reader :name
attr_reader :defs
def first_script
return @defs[0][0]
end
def super_name
return @defs[@superdef][1]
end
def super_script
return @defs[@superdef][0]
end
def first_loc
return first_script.loc
end
def super_loc
return super_script.loc
end
def add_definition(script, supername)
if !supername.nil? && super_name != supername then
print "Warning: Redefinition of class '#{name}' in #{script.loc} with inconsistent superclass '#{supername}'. Previous definition in #{super_loc} has superclass '#{super_name}'"
@superdef = @defs.size
end
@defs.push [script, supername]
end
def inconsistent?
return @superdef > 0
end
end
def self.get_class_defs(ctx)
classes = {}
expr = /^class\s+(\w+)\s*(?:<\s*(\w+)\s*)?$/
ctx.each_script do |script|
# Encoding is all kinds of messed up in RM
e = script.source.encoding
script.source.force_encoding Encoding.find("ASCII-8BIT")
script.source.scan(expr) do |groups|
name, supername = *groups
if !classes.include? name then
classes[name] = ClassInfo.new name, script, supername
else
classes[name].add_definition script, supername
end
end
script.source.force_encoding e
end
return classes
end
def self.overwrite_redefinitions(ctx)
classes = get_class_defs ctx
classes.each_pair do |name, cls|
if cls.inconsistent? then
print "Eliminating definitions of class '#{name}' before #{cls.super_loc}. First in #{cls.first_loc}"
cls.super_script.sub!(Regexp.new("^(class\\s+#{name}\\s*<\\s*#{cls.super_name}\\s*)$"),
"Object.remove_const :#{name}\n\\1")
end
end
end
def self.patch_scripts(ctx)
ctx.each_script do |script|
# Remove blacklisted scripts
if ctx.blacklisted? script then
print "Removed #{script.loc}: Blacklisted"
script.remove
next
end
# Encodings are a mess in RGSS. Can break Regexp matching
e = script.source.encoding
script.source.force_encoding "ASCII-8BIT"
# Apply patches
Patches.each do |patch|
break if patch.eval script
end
print "Patched #{script.loc}: #{script.log.join(', ')}" if script.log.size > 0
# Warn if Win32API references in source
if script.source.include? "Win32API.new" then
print "Warning: Script #{script.loc} uses Win32API."
require "Win32API.rb"
end
# Restore encoding
script.source.force_encoding e
end
ctx.run_delay_slot :after_patches
end
NoFilenameChars = "/$|*#="
def self.dump_scripts(ctx, opt)
# Dump all scripts to a folder specified by opt
if ctx.flag? opt then
dump = ctx[opt]
print "Dumping all scripts to %s" % dump
Dir.mkdir dump unless Dir.exist? dump
fn_format = "%0#{ctx.script_id_digits}d%s%s%s"
ctx.each_script do |script|
filename = fn_format % [script.index,
script.name.empty? ? "" : " ",
script.name.tr(NoFilenameChars, "_"),
script.source.empty? ? "" : ".rb"]
File.write File.join(dump, filename), script.source
end
end
end
# -------------------------------------------------------------------------
# Logic
# -------------------------------------------------------------------------
RgssVersionNames = ["Unknown", "XP", "VX", "VX Ace"]
@on_preload = []
@on_load = []
@on_boot = []
@ctx = nil
def self._run_preload
# Initialize
@ctx = ctx = Context.new $RGSS_SCRIPTS
ctx.read_system
ctx.read_env
# Preload[:vcode] = vcode
# Preload.Context.set(:vscode, vcode)
# puts vcode
# set :zeusrpgver, vcode
# set :zeusrpgver, vcode
# puts vcode
# set :zeusrpgver, vcode
# print "#{ctx[:zeusrpgver]}"
print "MKXP mkxp-z #{ctx[:mkxp_version]} RGSS #{ctx[:rgss_version]} (#{RgssVersionNames[ctx[:rgss_version]]})\n"
# Run preload hooks
@on_preload.each{|p| p.call ctx}
ctx.each_script do |script|
print "Script ##{script.index}: #{script.name}#{"\t[#{script.imported_key}]" if script.imported_key}"
end
# Patch Scripts
dump_scripts ctx, :dump_scripts_raw
patch_scripts ctx
overwrite_redefinitions ctx if ctx.flag? :redefinitions_overwrite_class
dump_scripts ctx, :dump_scripts_patched
# Try to inject hook after most (plugin) scripts are loaded but before game starts
ctx.last_script.source= "Preload._run_boot\n\n" + ctx.last_script.source
# Done
if ctx.flag? :dont_run_game then
print "KAWARIKI_MKXP_DRY_RUN is set, not continuing to game code"
exit 123
end
end
def self._run_load
@on_load.each {|p| p.call @ctx}
end
def self._run_boot
@on_boot.each {|p| p.call @ctx}
end
# -------------------------------------------------------------------------
# Callbacks for user-scripts
# -------------------------------------------------------------------------
# Register block to be called with preload context
def self.on_preload(&p)
@on_preload.push p
end
# Register block to be called after patches are applied
def self.on_load(&p)
@on_load.push p
end
# Register block to be called on RGSS boot
def self.on_boot(&p)
@on_boot.push p
end
end
_config = CFG
# puts "hhh"
def find_game_ini_in_directory(directory)
# Search for "game.ini" within the specified directory, case-insensitive
files = Dir.glob("#{directory}/Game.ini", File::FNM_CASEFOLD)
# If the file exists, return the full path
if files.any?
return files.first # Return the full path of the first match
else
return nil # Return nil if no file is found
end
end
game_ini_path = find_game_ini_in_directory(_config["gameFolder"].to_s)
# puts Dir.pwd
def checkini(file_path)
# Check if the file exists
if File.exist?(file_path)
# Read the content of the file
input_string = File.read(file_path, encoding: 'ASCII-8BIT')
# Match the content of the file and return the appropriate value
# Match the pattern in the input string and return corresponding values
if input_string =~ /rvdata2/
return 3
elsif input_string =~ /rvdata/
return 2
elsif input_string =~ /rxdata/
return 1
else
return 0 # Return nil if none of the patterns match
end
else
puts "File does not exist!"
return nil
end
end
vers = checkini(game_ini_path)
rgssversioncodes = ["Unknown", ":xp", ":vx", ":vxace"]
# vcode =
# set :zeusrpgver, vcode
ENV["vcode"] = rgssversioncodes[vers]
puts ENV["vcode"]
ENV["rpgvers"] = vers.to_s
# puts "nbnn"+ENV["vcode"]
# Ensure Zlib is loaded
Kernel.require 'zlib' unless Kernel.const_defined? :Zlib
# Load patch definitions
Kernel.require File.join(Preload::Path, 'patches.rb')
# Inject user scripts
Dir['*.kawariki.rb'].each do |filename|
Preload.print "Loading user script #{filename}"
Kernel.require filename
end
# Apply patches to scripts
Preload._run_preload
# Run load hooks just before control returns to MKXP to run the scripts
Preload._run_load

View file

@ -0,0 +1,31 @@
{
"$schema": "../versions.schema.json",
"format": 2,
"name": "mkxp",
"common": {
"slug": "{variant}-{version!v}-{platform}"
},
"versions": [
{
"variant": "mkxp-z",
"version": [2, 4, 0],
"platforms": ["linux-x86_64"],
"binary": "mkxp-z",
"url": "https://github.com/Orochimarufan/Kawariki/releases/download/mkxp-2.3.0-kk/mkxp-z-{version!v}-{platform}.tar.xz"
},
{
"variant": "mkxp-z",
"version": [2, 3, 1],
"platforms": ["linux-x86_64"],
"binary": "mkxp-z.x86_64",
"url": "https://github.com/Orochimarufan/Kawariki/releases/download/mkxp-2.3.0-kk/mkxp-z_2.3.1_x64.tar.xz"
},
{
"variant": "mkxp-z",
"version": [2, 3, 0],
"platforms": ["linux-x86_64"],
"binary": "mkxp-z.x86_64",
"url": "https://github.com/Orochimarufan/Kawariki/releases/download/mkxp-2.3.0-kk/mkxp-z_2.3.0_x64.tar.xz"
}
]
}

529
mkxp-z/mkxp.json Normal file
View file

@ -0,0 +1,529 @@
{
// Lines starting with '//' are comments.
//
// About filesystem paths specified in this config:
// to the directory containing the mkxp executable
// (the default behavior), or relative to the current
// working directory (when compiled with
// -DWORKDIR_CURRENT). All other paths are resolved
"gameFolder": "ggg",
// encrypted archives. Since this is JSON, any
// backslashes in paths need to be escaped (i.e. a
// single backslash becomes a double backslash). If
// that's too much hassle, you can use a single forward
// slash instead (even on Windows).
// Some influential environment variables, set them to either "1" or "0":
// "MKXPZ_WINDOWS_CONSOLE"
// - Enables/disables the extra console window on Windows. It appears by
// default whenever mkxp-z is started in debug mode.
// "MKXPZ_MACOS_METAL"
// - Setting this will influence the rendering backend used.
// Probably not a good idea to mess with it unless you have some kind of issue.
// OpenGL is default on x86, and Metal is the default on Apple Silicon.
// This takes priority over the config option if it's set.
// "MKXPZ_FOLDER_SELECT"
// - Allows the manual selection of the game's folder at startup.
// Only works on macOS at the moment.
// "MTL_HUD_ENABLED"
// - On macOS 13 (Ventura) and over, provided the Metal backend is used,
// this will enable a full performance HUD containing more details than
// the built-in framerate display.
// Specify the RGSS version to run under.
// Possible values are 0, 1, 2, 3. If set to 0,
// mkxp will try to guess the required version
// If this fails, the version defaults to 1.
// (default: 0)
//
// "rgssVersion": 1,
// Continuously display average FPS in window title.
// This can always be toggled with F2 at runtime.
// (default: disabled)
//
// "displayFPS": false,
// Continuously print average FPS to console.
// This setting does not affect the window title
// FPS display toggled via F2
// (default: disabled)
//
// "printFPS": false,
// Game window is resizable
// (default: enabled)
//
// "winResizable": true,
// Start game in fullscreen (this can
// always be toggled with Alt-Enter at runtime)
// (default: disabled)
//
// "fullscreen": false,
// Preserve game screen aspect ratio,
// as opposed to stretch-to-fill
// (default: enabled)
//
// "fixedAspectRatio": true,
// Apply smooth interpolation when game screen
// is upscaled
// 0: Nearest-Neighbor
// 1: Bilinear
// 2: Bicubic
// 3: Lanczos3
// 4: xBRZ
// (default: 0)
//
// "smoothScaling": 0,
// Apply smooth interpolation when game screen
// is downscaled (same values as smoothScaling)
// (default: 0)
//
// "smoothScalingDown": 0,
// Apply smooth interpolation when bitmaps
// are upscaled (same values as smoothScaling)
// (default: 0)
//
// "bitmapSmoothScaling": 0,
// Apply smooth interpolation when bitmaps
// are downscaled (same values as smoothScaling)
// (default: 0)
//
// "bitmapSmoothScalingDown": 0,
// Apply mipmap interpolation when game screen
// or bitmaps are downscaled (requires
// "smoothScalingDown": 1 or "bitmapSmoothScalingDown": 1)
// (default: false)
//
// "smoothScalingMipmaps": false,
// Sharpness when using Bicubic scaling.
// A good starting range is 0 to 100,
// but you may wish to go outside that range in either direction.
// (default: 100)
//
// "bicubicSharpness": 100,
// Scaling factor for xBRZ interpolation
// (set to at least the ratio of your window size
// to the game's native resolution)
// (default: 1.0)
//
// "xbrzScalingFactor": 4.0,
// Replace the game's Bitmap files with external high-res files
// provided in the "Hires" directory.
// (You'll also need to set the below Scaling Factors.)
// (default: disabled)
//
// "enableHires": false,
// Scaling factor for textures (e.g. Bitmaps)
// (higher values will look better if you use high-res textures)
// (default: 1.0)
//
// "textureScalingFactor": 4.0,
// Scaling factor for screen framebuffer
// (higher values will look better if you use high-res textures)
// (default: 1.0)
//
// "framebufferScalingFactor": 4.0,
// Scaling factor for tileset atlas
// (higher values will look better if you use high-res textures)
// (default: 1.0)
//
// "atlasScalingFactor": 4.0,
// Sync screen redraws to the monitor refresh rate
// (default: disabled)
//
// "vsync": false,
// Specify the window width on startup. If set to 0,
// it will default to the default resolution width
// specific to the RGSS version (640 in RGSS1, 544
// in RGSS2 or higher).
// (default: 0)
//
// "defScreenW": 640,
// Specify the window height on startup. If set to 0,
// it will default to the default resolution height
// specific to the RGSS version (480 in RGSS1, 416
// in RGSS2 or higher).
// (default: 0)
//
// "defScreenH": 480,
// Override the game window title
// (default: none)
//
// "windowTitle": "Custom Title",
// Enforce a static frame rate
// (0 = disabled)
//
// "fixedFramerate": 0,
// Skip (don't draw) frames when behind.
// Can be changed at runtime, but this is the
// default value when the game starts.
// (default: disabled)
//
// "frameSkip": false,
// Use a fixed framerate that is approx. equal to the
// native screen refresh rate. This is different from
// "fixedFramerate" because the actual frame rate is
// reported back to the game, ensuring correct timers.
// If the screen refresh rate cannot be determined,
// this option is force-disabled.
// This option may be force-disabled at build time.
// (default: disabled)
//
// "syncToRefreshrate": false,
// A list of fonts to render without alpha blending.
// (default: none)
//
// "solidFonts": [
// "Arial",
// "Times New Roman",
// ],
// Prefer the use of Metal over OpenGL on macOS.
// This defaults to false under Intel machines,
// and true under ARM/Apple Silicon ones (which
// merely emulate OpenGL anyway)
//
// Try changing this if you have graphics problems.
// Metal is far better, but ANGLE may not initialize correctly
// on some Intel machines.
//
// On Apple Silicon it's probably better to not touch it.
// Emulated OpenGL is buggy, and it will also break things like
// the Steam overlay.
// (default: true if Apple Silicon, false if Intel)
//
// "preferMetalRenderer": true,
// Work around buggy graphics drivers which don't
// properly synchronize texture access, most
// apparent when text doesn't show up or the map
// tileset doesn't render at all
// (default: disabled)
//
// "subImageFix": false,
// Enable framebuffer blitting if the driver is
// capable of it. Some drivers carry buggy
// implementations of this functionality, so
// disabling it can be used as a workaround.
// Does nothing on macOS. Force-disabled when
// smoothScaling or smoothScalingDown isn't
// Nearest-Neighbor or Bilinear.
// (default: disabled)
//
// "enableBlitting": false,
// Limit the maximum size (width, height) of
// most textures mkxp will create (exceptions are
// rendering backbuffers and similar).
// If set to 0, the hardware maximum is used.
// This is useful for recording traces that can
// be played back on machines with lower specs.
// (default: 0)
//
// "maxTextureSize": 0,
// Scale up the game screen by an integer amount,
// as large as the current window size allows, before
// doing any last additional scalings to fill part or
// all of the remaining window space (or none at all
// if lastMileScaling is disabled).
// If fixedAspectRatio is disabled, the integer scale
// factors in horizontal and vertical direction can differ
// depending on how much space is available, otherwise
// they are forced to the smaller of the two.
// (default: disabled)
//
// "integerScalingActive": false,
// When integer scaling is enabled, this option controls
// whether the scaled game screen is further scaled
// (with linear interpolation when smoothScaling is enabled)
// to fill the rest of the game window.
// Note that this option still respects fixedAspectRatio.
// (default: enabled)
//
// "integerScalingLastMile": true,
// Set the base path of the game to '/path/to/game'
// (default: executable directory)
//
// Use either right or left Alt + Enter to toggle
// fullscreen
// (default: disabled)
//
// "anyAltToggleFS": false,
// Enable F12 game reset
// (default: enabled)
//
// "enableReset": true,
// Enable F1/keybinding menu
// (default: enabled)
//
// "enableSettings": true,
// Allow symlinks for game assets to be followed
// (default: disabled)
//
// "allowSymlinks": false,
// Organisation / company and application / game
// name to build the directory path where mkxp
// will store game specific data (eg. key bindings).
// If not specified, mkxp-z will use a folder based
// on the name of the game, if possible, defaulting
// to "." for Org and "mkxp-z" for App otherwise.
// (default: none)
//
// "dataPathOrg": "mycompany",
// "dataPathApp": "mygame",
// Set the game window icon to 'path/to/icon.png'
// Only functions on Linux.
// (default: none)
//
// "iconPath": "/path/to/icon.png",
// Instead of playing an RPG Maker game,
// execute a single plain text script instead
// (default: none)
//
// "customScript": "/path/to/script.rb",
// Define raw scripts (e.g. compatibility wrappers)
// to be executed before the actual Scripts.rxdata
// execution starts
// (default: none)
//
// "preloadScript": [
// "scripts/preload/ruby_classic_wrap.rb",
// "scripts/preload/mkxp_wrap.rb",
// "scripts/preload/win32_wrap.rb",
// ],
"preloadScript": [
"Kawariki-patches/preload.rb",
],
// Index all accesible assets via their lower case path
// (emulates windows case insensitivity)
// (default: enabled)
//
// "pathCache": true,
// Add 'rtp1', 'rtp2.zip' and 'game.rgssad' to the asset search path
// (multiple allowed). You can use folders, RGSS archives, and any archive
// formats supported by PhysicsFS; see the compatibility list at:
// https://www.icculus.org/physfs/docs/html/
// (default: none)
//
"RTP": [
"RGSS3/RPGVXAce",
"RGSS2/RPGVX",
"RGSS/Standard"],
// Similar to the RTP option, except items are loaded before
// the game archive and folder, for incremental game updates
// or modding.
// (default: none)
//
// "patches": [
// "/path/to/patch1.zip",
// "/path/to/patch2",
// ],
// Use the script's name as filename in warnings and error messages
// (default: enabled)
//
// "useScriptNames": true,
// Font substitutions allow drop-in replacements of fonts
// to be used without changing the RGSS scripts,
// eg. providing 'Open Sans' when the game thinkgs it's
// using 'Arial'. Font family to be substituted and
// replacement family are separated by one sole '>'.
// Be careful not to include any spaces.
// This is not connected to the built-in font, which is
// always used when a non-existing font family is
// requested by RGSS.
// (default: none)
//
// "fontSub": [
// "Arial>Open Sans",
// "Times New Roman>Liberation Serif",
// ],
// Because mkxp is usually distributed as a stand alone
// build, no predefined load paths are initialized
// ($:, $LOAD_PATH) in the MRI backend. With this option,
// they can be specified manually (eg. when using a system
// libruby.so). It is however recommended to statically
// link all required gems into libruby.so.
// (default: none)
//
// "rubyLoadpath": [
// "/usr/lib64/ruby/",
// "/usr/local/share/ruby/site_ruby",
// ],
// Determines whether MJIT is enabled. This probably
// won't work unless you also have the header file
// that it needs. Only works with Ruby 2.6 or higher.
// This Ruby feature is experimental.
// (default: false)
//
// "JITEnable": false,
// Determines what level of verbosity to use when
// logging MJIT events. Starts at 0, which is next
// to nothing. Set it higher to see more.
// (default: 0)
//
// "JITVerboseLevel": 0,
// Determines how many compiled methods that Ruby
// will keep in its MJIT cache.
// (default: 100)
//
// "JITMaxCache": 100,
// Determines how many times a function has to be
// called before it is compiled by MJIT.
// (default: 10000)
//
// "JITMinCalls": 10000,
// Determines whether YJIT is enabled.
// Only works with Ruby 3.1 or higher.
// This Ruby feature is experimental.
// (default: false)
//
// "YJITEnable": false,
// SoundFont to use for midi playback (via fluidsynth)
// (default: none)
//
// "midiSoundFont": "Audio/BGM/GMGSx.sf2",
// Activate "chorus" effect for midi playback
//
// "midiChorus": false,
// Activate "reverb" effect for midi playback
//
// "midiReverb": false,
// Number of OpenAL sources to allocate for SE playback.
// If there are a lot of sounds playing at the same time
// and audibly cutting each other off, try increasing
// this number. Maximum: 64.
//
// "SESourceCount": 6,
// Number of streams to open for BGM tracks. If the game
// needs multitrack audio, this should be set to as many
// available tracks as the game needs. Maximum: 16.
//
// "BGMTrackCount": 1,
// The Windows game executable name minus ".exe". By default
// this is "Game", but some developers manually rename it.
// mkxp needs this name because both the .ini (game
// configuration) and .rgssad (encrypted data archive) must
// carry the same name minus their extension, and we cannot
// guess the executable's name.
// You could just as well rename them both to "Game.ini" and
// "Game.rgssad", but specifying the executable name here
// is a tiny bit less intrusive.
//
// "execName": "Game",
// You can define alternate terminology for the different
// inputs recognized by RPG Maker. A, B, C, X, Y, Z, L, and R
// can all be set using this dictionary, and will be displayed
// on the F1 menu. This is only a cosmetic effect, so it will
// have no effect on the game's scripts.
//
// "bindingNames": {
// "c": "Confirm",
// "b": "Cancel",
// "x": ...,
// },
// Dump tile atlas (for debugging purposes)
// (default: false)
//
// "dumpAtlas": false,
}