diff --git a/Kawariki-patches/README.md b/Kawariki-patches/README.md new file mode 100644 index 0000000..a3ec7b2 --- /dev/null +++ b/Kawariki-patches/README.md @@ -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: +- `/mkxp/mkxp.json`: Global mkxp config +- `/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 `/mkxp/mkxp.json`: +```json +{ + "RTP": ["/path/to/rtp"] +} +``` + +### Environment Variables +- `KAWARIKI_MKXP_DUMP_SCRIPTS=`: Dump scripts to before applying patches +- `KAWARIKI_MKXP_DUMP_PATCHED_SCRIPTS=`: Dump scripts to after applying patches +- `KAWARIKI_MKXP_FILTER_SCRIPTS=[,...]`: 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 + + + +[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/ diff --git a/Kawariki-patches/libs/PreloadIni.rb b/Kawariki-patches/libs/PreloadIni.rb new file mode 100644 index 0000000..87896ff --- /dev/null +++ b/Kawariki-patches/libs/PreloadIni.rb @@ -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 diff --git a/Kawariki-patches/libs/Win32API.rb b/Kawariki-patches/libs/Win32API.rb new file mode 100644 index 0000000..0ecefaa --- /dev/null +++ b/Kawariki-patches/libs/Win32API.rb @@ -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 diff --git a/Kawariki-patches/libs/Win32APIl.rb b/Kawariki-patches/libs/Win32APIl.rb new file mode 100644 index 0000000..af1cdef --- /dev/null +++ b/Kawariki-patches/libs/Win32APIl.rb @@ -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 diff --git a/Kawariki-patches/libs/XP_TilemapOverrideLib.rb b/Kawariki-patches/libs/XP_TilemapOverrideLib.rb new file mode 100644 index 0000000..9eabdbf --- /dev/null +++ b/Kawariki-patches/libs/XP_TilemapOverrideLib.rb @@ -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 diff --git a/Kawariki-patches/libs/ruby18.rb b/Kawariki-patches/libs/ruby18.rb new file mode 100644 index 0000000..d80d367 --- /dev/null +++ b/Kawariki-patches/libs/ruby18.rb @@ -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 diff --git a/Kawariki-patches/patches.rb b/Kawariki-patches/patches.rb new file mode 100644 index 0000000..a602ede --- /dev/null +++ b/Kawariki-patches/patches.rb @@ -0,0 +1,237 @@ +# 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("Advanced Text System fix") + # .include?('Advanced Text System') + # .include?('modern algebra (rmrk.net)') + # # .remove!, + # .replace!("Advanced-Text-System.rb"), + Patch.new("NWConst::Warp plugin fix") + .include?('module NWConst::Warp') + .include?('@popup_confirm_window.select(0)') + .sub!('@popup_confirm_window.select(0)', "items = @popup_confirm_window.instance_variable_get(:@list)\nif items.is_a?(Array) && !items.empty?\n@popup_confirm_window.select(0)\nend"), + Patch.new("NWRegexpBaseItemfix plugin fix") + .include?('module NWConst::Shop') + # .replace!("NWRegexpBaseItemfix.rb"), + .sub!('@select_actor_window.show.activate.select(0)', "if @select_actor_window.show.activate.is_a?(Array) && @select_actor_window.show.activate.empty?\n@select_actor_window.show.activate.select(0)\nend"), + Patch.new("NWConst::Synthesize plugin fix") + .include?('module NWConst::Synthesize') + .include?('Container.item') + # .replace!("NWRegexpBaseItemfix.rb"), + .sub!('@popup_confirm_window.select(0)', "if @popup_confirm_window.is_a?(Array) && @popup_confirm_window.empty?\n@popup_confirm_window.select(0)\nend") + .sub!('@before_actors_window.show.activate.select(0)', "if @before_actors_window.show.activate.is_a?(Array) && @before_actors_window.show.activate.empty?\n@before_actors_window.show.activate.select(0)\nend") + .sub!('@after_actors_window.show.activate.select(0)', "if @after_actors_window.show.activate.is_a?(Array) && @after_actors_window.show.activate.empty?\n@after_actors_window.show.activate.select(0)\nend"), + Patch.new("AudioUtilities plugin fix") + .include?('waveOutOpen=Win32API.new("winmm.dll","waveOutOpen","plplll","l")') + .include?('def pbPlaySoundData(samples,volume,async=false,sampleFreq=11025)') + # .remove!, + # .replace!("dummyAudioUtilities.rb") + .sub!(/when (\d+):/, 'when \1'), + Patch.new("dummyPSystem_Utilities plugin fix") + .include?('pbNextComb') + # .include?('def pbIsJsonString') + # .remove!, + .replace!("dummyPSystem_Utilities.rb"), + # .sub!(/when (\d+):/, 'when \1'), + 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("Audio_EX2 WF-RGSS patch") #【WF-RGSS】共通rev29 base + .include?("module WFRGSS_AudioEX") + # .sub!("return true if @audio__init", "return true"), + # .include?("self.to_widechar(") + .replace!("dummy_Audio_EX2.rb"), + Patch.new("WF-RGSS base patch") #【WF-RGSS】共通rev29 base + .include?("MultiByteToWideChar") + .include?("to_widechar(") + .replace!("testencode.rb"), + Patch.new("window check content height fix (mgq paradox)") #Window_Base + .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") + .sub!("result = text.to_s.clone","result = text.to_s.clone\nresult = result.dup if result.frozen?"), + 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("tktk_bitmap dll test debug") + # .imported?(nil) + # .include?("DLL_NAME = 'tktk_bitmap'") + # # .remove!, + # .replace!("bitmap_tktk.rb"), + Patch.new("HN_Light tktk dll disable ") + .imported?(nil) + .include?("HN_Light version") + .include?("tktkgame") + .sub!("@dark_sprite.update", "@dark_sprite.dispose"), + # .replace!("lamp.rb"), + Patch.new("message_npi fix") + .imported?(nil) + .include?("module MessageTextDataBase") + .include?("BlueRedZone") + # #.sub!("@dark_sprite.update", "@dark_sprite.dispose"), + .replace!("message_npi.rb"), + Patch.new("Flirt quest") + .imported?(nil) + .include?('class Spriteset_BattleUnit') + .include?('Spriteset_Kiseki') + .sub!(/\bsuper\b(?!\s*\()/, 'super()'), + + ] +end diff --git a/Kawariki-patches/patchesm.rb b/Kawariki-patches/patchesm.rb new file mode 100644 index 0000000..5f0d430 --- /dev/null +++ b/Kawariki-patches/patchesm.rb @@ -0,0 +1,178 @@ +# 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("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 diff --git a/Kawariki-patches/ports/Advanced-Text-System-.rb b/Kawariki-patches/ports/Advanced-Text-System-.rb new file mode 100644 index 0000000..d42e4e4 --- /dev/null +++ b/Kawariki-patches/ports/Advanced-Text-System-.rb @@ -0,0 +1,3708 @@ +#============================================================================== +# Advanced Text System +# Version: 3.0c +# Author: modern algebra (rmrk.net) +# Date: September 7, 2010 +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Thanks: +# *Zeriab* for his tutorial on RegExp, but most of all for being my mentor +# and friend. Without him, this script, and all my other scripts, will +# never have been made. +# For ideas on improving this script through bug reports or suggestions: +# Arrow-1, Irock, Stonewall, PhantomH, Seasons in the Abyss, Aindra, Arion, +# Okogawa, Megatronx, Charbel, Adrien., dricc, Raukue, EricDahRed, redyugi, +# HeartofShadow, Woratana, tsy0302, Kayo, deadnub. +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Description: +# +# This scripts adds a whole bunch of features to message boxes, making them +# much prettier and with a lot more functionality than the default. What and +# how to use each feature will be exhaustively detailed in the instructions, +# but for now I will just give a list of a few of the staple features: +# +# ~Appended Text~ +# A staple feature of ATS, this will combine the messages of subsequent text +# events (that share properties). It is useful particularly when combined +# with text scrolling. +# +# ~Text Scrolling~ +# This feature allows longer text messages to scroll upwards, so that for +# longer messages, the contents of the window will scroll upwards when it +# reaches the bottom, allowing the player to read what has come before and +# ignore the annoying new page requirements. This feature has been beefed up +# in ATS 3.0, with the scroll now being much smoother and the speed user- +# definable, in addition to the new feature of Scroll Review, which, once the +# message is finished, allows the player to scroll upwards to see any text he +# or she might have missed. Moreover, you can also scroll by page instead. +# +# ~Paragraph Format~ +# Always a staple feature in the ATS, turning this feature on allows you to +# avoid the hassle that comes with making the text fit within the window by +# analyzing the exact length with reference to the grey arrows. Now you can +# type freely and know that nothing will get cut off. This feature takes on +# additional importance in ATS 3.0 with the addition of a special message +# codes like \S[] and \S![], more on which will be discussed later. As +# always, you can still manually define line breaks with the \LB code. As in +# previous versions, you can also justify text, meaning that the letters will +# be spaced out so as to exactly cover the width of the message box. Unlike +# previous versions, this paragraph formatter is integrated with the ATS in +# order to better accomodate text features and therefore does not require a +# separate Paragraph Formatter script to work. +# +# ~Advanced Choices~ +# Another staple feature of the ATS, you have been able to append choices +# from subsequent choice branches and show them all in a separate window. +# This feature has been vastly expanded on in ATS 3.0. In earlier versions, +# many of the features respecting advanced choices, such as appending them +# and tying choices to switches, were available only if using the choice +# window. In ATS 3.0, these features have been included in the traditional +# in-message window. Don't want to use the choice window but do want to +# exclude some choices unless a particular switch is ON? You can do that now. +# In addition to all of the old features, there are quite a few new ones that +# will make your choices more dynamic. One is the ability to make an the +# cursor pass over an option. This means it is a perfect way to include +# headings or blank spaces in your choice box. Another new feature are the +# disable codes which allow you to make a choice unselectable, so when the +# player tries to select it the buzzer sound is played and nothing happens. +# Further, there is an external message property that can be set which you +# can use to automatically add certain properties to modify the choice text +# if it is disabled, such as a command to reduce the opacity of it. In +# addition, you can automatically modify all choice texts at once - want to +# add a larger indent or make all choices blue - you can do that. Finally, +# another unique feature of ATS choices is the \+{} code. Placed in a comment +# directly below a when branch of a choice branch, this allows you to add +# whatever text you want to that choice, effectively ignoring the limitations +# on choice message size. This means you can make your choices sentences +# instead of simple yes or nos. In addition to this, another new feature of +# ATS 3.0 choices is that choice messages can be longer than one line and +# that is recognized both in the choicebox and the new and improved default +# choice scheme. The last new feature added to choices in ATS 3.0 is the +# ability to set a help window for it. This means it will set a window whose +# contents will change depending on which choice the player is hovering over. +# +# ~Advanced Faces~ +# From the very start, animated faces have been a part of the ATS. This +# feature allows you to have the faces animate for every few letters drawn - +# perfect to make it look like the face is talking. However, this is not all +# the special features you gain with faces. As always, you can also use much +# larger or smaller faces, mirror them, set them to the right side of the +# text box, or move them anywhere you like, change the opacity, or surround +# it with a window. New in ATS 3.0, you can now define the exact size of the +# border of the window if using it, or you can use another sprite for it +# (just like the dim background of the Message Window). Also new in ATS 3.0, +# you can scroll the face in when it first appears either horizontally or +# vertically, or fade it in. You can also choose to make the face appear over +# or under the message window, and change the blend type. +# +# ~Letter Control~ +# This has always been a feature of the ATS and it allows you to control the +# speed that text is drawn, as well as putting in pauses at will. It also +# allows you to specify a sound, and you can even have it vary the pitch with +# every letter so that it mimics the sound of a voice. New in ATS 3.0, you +# can control the speed text is drawn not only through the \> and \< codes, +# but more directly via the \S[] command, where you can either set it to add +# or subtract a number from the current speed, or you can set it directly. +# See the section on this code in Special Message Codes for details. +# +# ~Advanced Message Window~ +# There are also a number of special features related to the appearance and +# position of the message window. Most of these should be familiar to users +# of ATS 2.0. You can manually control the size and exact position of the +# text box, or you can set it to automatic. Further, you can use the +# :fit_window_to_text property to set the width of the window to be only as +# long as it needs to be to accomodate the longest line. As always, you can +# set what windowskin to use, the font, how much space for each line, and the +# dim background. New features include the :do_not_obscure property, which, +# when using default positioning, will ensure that the characters specified +# in the :obscure_characters array. This feature will be ignored if manually +# setting position however. The only thing it does is, if you have the +# position set to bottom, but that would cover the player and you include the +# player in your obscure_characters array, then it would move the message box +# to the top instead. As always, you can use \OC[], \UC[], \LC[], and \RC[] +# to position the window in reference to a character. Something new is the +# \E[] command, which will set it above the character, unless there is not +# enough room, in which case it sets it below the character. Another new +# feature is speech tags, which, when using \OC[], \UC[], or \E[] codes will +# place a tag from the character speaking to the message window if this +# feature is enabled. +# +# ~Word Boxes~ +# New to ATS 3.0, this is a very simple feature that works akin to the Gold +# or Name window. Basically, it allows you to set a one line message to +# appear instantly. It can be used, for instance, if you want to show the +# value of a variable in the corner while showing this message or choice. +# Naturally, name boxes have also been retained from ATS 2.0 +# +# ~Graphic Novel Support~ +# New to ATS 3.0, this feature, when activated, allows the player to +# completely hide and prevent from updating every message related window for +# as long as the player holds a button you choose or, if you wish, it can be +# a toggle rather than a press. It is useful if you have a game that relies +# heavily on graphics and you want to give the player the opportunity to just +# look at the background, like in many graphic novel games. +# +# ~Move While Showing~ +# Also new to ATS 3.0, this is a feature that, when activated, allows the +# player to move around while a message is showing. However, when a choice +# selection is active, this feature will be disabled. +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Instructions: +# +# Place this script in its own slot in the Script Editor (F11) above Main +# and below Materials. This script is not compatible with most other message +# systems, so you may need to remove any other message systems. If you are +# upgrading to this from ATS 2.0, you will need to also install the +# conversion patch, located in this script's thread at RMRK. +# +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~Special Message Codes~ +# These codes work in all ATS windows. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# You will recognize many of these from previous versions of the ATS. A lot +# are new though. +# \lb - Line break. Go to next line. +# \v[x] - Draw the value of the variable with ID x. +# \n[x] - Draw the name of the actor with ID x. +# \c[x] - Set the colour of the text being drawn to the xth colour of the +# Windowskin palette. 0 is the normal color and 16 is the system color. +# \c[#RRGGBB] - Set the colour of the text being drawn to any colour, using +# hexadecimal. You can set each colour (red, green, blue) to anything +# from 0-255 (00-FF). You must use hexadecimal values. +# \p[x] OR \pid[x] - Draw the ID of the actor in the xth position in the party. +# \ni[x] - Draw the name of the item with ID x. +# \nw[x] - Draw the name of the weapon with ID x. +# \na[x] - Draw the name of the armor with ID x. +# \ns[x] - Draw the name of the skill with ID x. +# \nt[x] - Draw the name of the state with ID x. +# \nc[x] - Draw the name of the class with ID x. +# \ne[x] - Draw the name of the event with ID x on the current map. +# \nm[x] - Draw the name of the enemy with ID x. +# \nl[x] - Draw the name of the element with ID x. +# \nv[x] - Draw the name of the variable with ID x. +# \nsw[x] - Draw the name of the switch with ID x. +# \np[x] - Draw the name of the actor in the nth position in the party. +# \map - Draw the name of the map the player is currently on. +# \map[x] - Draw the name of the map with ID x. +# \di[x] - Draw the description of the item with ID x. +# \dw[x] - Draw the description of the weapon with ID x. +# \da[x] - Draw the description of the armor with ID x. +# \ds[x] - Draw the description of the skill with ID x. +# \pi[x] - Draw the price of the item with ID x. +# \pw[x] - Draw the price of the weapon with ID x. +# \pa[x] - Draw the price of the armor with ID x. +# \i#[x] - Draw the number of the item with ID x that the party posesses. +# \w#[x] - Draw the number of the weapon with ID x that the party posesses. +# \a#[x] - Draw the number of the armor with ID x that the party posesses. +# \ac[x] - Draw the class of the actor with ID x. +# \i[x] - Draw the icon with index x. +# \ii[x] - Draw the icon of the item with ID x. +# \wi[x] - Draw the icon of the weapon with ID x. +# \ai[x] - Draw the icon of the armor with ID x. +# \si[x] - Draw the icon of the skill with ID x. +# \ti[x] - Draw the icon of the state with ID x. +# \fn[fontname] - Change the font to fontname +# \fs[x] - Change the fontsize to x. +# \fa[x] - Change the alpha value (opacity) of the font to x. +# \b - Turn bold on. Text drawn after this code will be bolded. +# /b - Turn bold off. +# \i - Turn italic on. Text drawn after this code will be italicized. +# /i - Turn italic off. +# \s - Turn shadow on. Text drawn after this code will have a shadow. +# /s - Turn shadow off. +# \u - Turn underline on. Text drawn after this code will be underlined. +# /u - Turn underline off. +# \hl[x] - Turn highlight on. Text drawn after this code will be highlighted +# with colour x from the windowskin palette +# /hl OR \hl[-1] - Turn highlight off. +# \l - align the text to the left +# \r - align the text to the right +# \c - align the text to the centre. +# \t - Tab. Draws the next character at the nearest pixel that is a +# multiple of 32. Doesn't work well with the :justified_text property. +# \x[n] - Sets the x position for drawing directly to n. +# \f[key] - Draw the value corresponding to that key in the FILTERS array. If +# the key has quotation marks around it, you need to put "key" +# \s[x,text] - Will only draw text if the switch with ID x is ON. +# \s![x,text] - Will only draw text if the switch with ID x is OFF. +# \vocab[method] - Will draw whatever Vocab.method returns, if it is a valid +# method call. Suitable values for method are: level, level_a, hp, +# hp_a, mp, mp_a, atk, def, spi, agi, weapon, armor1, armor2, armor3, +# armor4, weapon1, weapon2, attack, skill, guard, item, equip, status, +# save, game_end, fight, escape, new_game, shutdown, to_title, gold, +# continue, cancel +# \actor_method[x] - This will draw whatever actor.method returns for whoever +# actor x. Some suitable values for method are: atk, def, spi, agi, +# level, exp, name, hp, maxhp, mp, maxmp, & any other methods that +# return values from Game_Actor. +# \i_method[x] - This will draw whatever item.method returns for the item with +# ID x. Some suitable values for method are: name, description, +# base_damage, variance, atk_f, spi_f, price, hp_recovery_rate, +# hp_recovery, mp_recovery_rate, mp_recovery, parameter_points, note, +# n, & any other methods that return values from RPG::Item. Also, note +# will return all of the contents of the note field, whereas n will +# only return notefield text located between a \msg[text]msg/ code. +# \w_method[x] - This will draw whatever weapon.method returns for the weapon +# with ID x. Some suitable values for method are: name, description, +# price, hit, atk, def, spi, agi, note, n, & any other methods that +# return values from RPG::Weapon. Also, note will return all +# of the contents of the note field, whereas n will only return +# notefield text located between a \msg[text]msg/ code. +# \a_method[x] - This will draw whatever armor.method returns for the armor +# with ID x. Some suitable values for method are: name, description, +# price, eva, atk, def, spi, agi, note, n, & any other methods that +# return values from RPG::Armor. Also, note will return all +# of the contents of the note field, whereas n will only return +# notefield text located between a \msg[text]msg/ code. +# \s_method[x] - This will draw whatever skill.method returns for the skill +# with ID x. Some suitable values for method are: name, description, +# base_damage, variance, atk_f, spi_f, hit, mp_cost, note, n, & any +# other methods that return values from RPG::Weapon. Also, note will +# return all of the contents of the note field, whereas n will only +# return notefield text located between a \msg[text]msg/ code. +# \t_method[x] - This will draw whatever state.method returns for the state +# with ID x. Some suitable values for method are: name, atk_rate, +# def_rate, spi_rate, agi_rate, message1, message2, message3, +# message4, note, n, & any other methods that return values from +# RPG::State. Also, note will return all of the contents of the note +# field, whereas n will only return notefield text located between a +# \msg[text]msg/ code. +# \enemy_method[x] - This will draw whatever enemy.method returns for whoever +# the enemy with ID x is. Some suitable values for method are: name, +# atk, def, spi, agi, hit, eva, exp, gold, note, n, & any other +# methods that return values from RPG::Enemy. Also, note will return +# all of the contents of the note field, whereas n will only return +# text located between a \msg[text]msg/ code. +# \#{code}# - This will evaluate code. So, if you know scipting, you can place +# any code there and it will draw whatever is returned by it. For +# instance: \#{$game_system.save_count}# would draw the number of +# times the player has saved the current game. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~Message Box specific codes~ +# These codes will only work in the Message Window. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# \g - Shows a window with the party's gold. Closes if already open +# \nb[name] - Shows a name box with name displayed in the box +# /nb - Closes the namebox +# \wb[word] - shows the word in its own window, similar to the gold window. +# /wb - Closes a wordbox +# \. - Wait 15 frames (1/4 second) before drawing the next letter. +# \| - Wait 60 frames (1 second) before drawing the next letter. +# \w[x] - Wait x frames before drawing the next letter. +# \! - Pause. Make the message wait for player input before continuing. +# \^ - Skip the next pause without waiting for player input. +# \> - Speed up the text drawing by reducing wait time between letters by +# one frame. +# \< - Slow down the text drawing by increasing wait time between letters by +# one frame. +# \S[x] - Change the speed the text draws by adding x to the current time +# between drawing letters. Thus, \s[2] is the equivalent of \<\< and +# \s[-3] is the equivalent of \>\>\>. You can also directly set the +# speed by putting an equal sign, so \s[=0] would set the time to one +# frame between drawing letters, and \s[=-1] would set it to instant. +# \@ - Turn on Show line fast - the line up to /@ will be shown instantly +# /@ - Turn off Show line fast. +# \@@ - Turn on Show message fast. This will show the entire message +# instantly, at least until the next scroll or it hits a /@@ +# /@@ - Turn off Show message fast. +# \% - Disable Text Skip through user input +# \se[sound effect name] - Plays a sound effect +# \me[music effect name] - Plays a musical effect +# \ani[target_id,animation_id] - Shows animation_id on target_id. 0 => player, +# other numbers indicate the ID of the event +# \bln[target_id,balloon_id] - Same as ani, but shows a balloon +# \af[x] - Show the face of the actor with ID x. +# \pb - Page Break. Clear the contents and start drawing from the first line +# \oc[x] - positions the message box over a character. 0 => player; when >1, it +# will show over the event with that ID on the map. +# \uc[x] - same as \oc, but places box under character +# \lc[x] - same as \oc, but places box to left of character +# \rc[x] - same as \oc, but places box to right of character +# \e[x] - same as \oc, but if the box is too tall to comfortably fit, is moved +# below the character instead. +# \mxy[x, y] - Set the position of the message window to x, y. +# \fxy[x, y] - Set the position of the face window to x, y. +# \nxy[x, y] - Set the position of the name window to x, y. +# \#!{code}# - This will evaluate code at the time the window reaches this code +# when drawing. It does not put the result of the code into the message +# but is instead intended to be evaluated. For instance: \#!{new_page}# +# would perform the same function as \pb. You need to know syntax! +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~Choice Branch Specific Codes~ +# These codes only work in choice branches. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# \skip - Placing this in a choice text means that the player will not be able +# to hover over or select this option, but will skip to the next one. +# Perfect for making subheadings for your choices, or blank spaces. +# \soff[x] - A choice with this in it will only appear in the branch if the +# switch with ID x is OFF. +# \son[x] - Same as soff[x], but it will only appear if switch x is ON. +# \d[x] - A choice with this in it will be disabled (unselectable) if the +# switch with ID x is OFF. It will still show up, and the player can +# hover over it, but he or she will be prevented from selecting it. +# \d![x] - Same as \d[x], except it will be disabled if switch x is ON. +# \wb[text] - This code will create a help window. When the player hovers over +# that choice, it will show text in the help window. This allows you to +# explain the choice or make any content in the help window dependent +# on which choice the player is on. +# \+{text} - This is actually a code you can put in a comment that is directly +# below the when branch of a choice, and it will add text to the +# choice. This effectively ignores the normal limitations on the size +# of a message in a choice, allowing you to make longer texts for +# choices. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~Message Properties~ +# The following are properties of the message window that are set prior to +# calling the message and govern many properties. You set the default values +# to the constants of the same name in Game_ATS, starting at line 766. Those +# will be the values that each game starts out with. You can then change them +# on a message to message basis through either of the codes: +# ats_next (:property, new_value) +# or: +# $game_message.property = new_value +# However, it will reset to the default after the next message is processed. +# If you want to change it on a permanent basis, you have to use either: +# ats_all (:property, new_value) +# or: +# $game_ats.property = new_value +# $game_message.property = new_value +# +# All of the properties and what types of values are expected for each are +# listed below. Note that if you use the ats_next or ats_all commands, you +# have to retain the : in front of the name. If you use the $game_ats or +# $game_message route then you don't use the : +# +# You can reset $game_ats to the constants with: $game_ats.reset +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :max_lines - This is the maximum number of lines to show at a time before +# either going to a new page or scrolling. Default: 4 +# :message_speed - This is the number of frames to wait in between drawing +# letters. -1 is instant. 0 is 1 frame. Default: 0 +# :skip_disabled - Whether the player can speed text up by pressing ENTER. +# :append_text - Whether to include all text from subsequent text commands in +# the next message. true => append; false => do not append. Default: true +# :append_choice - Whether to include all choices from subsequent choice +# choice branches in the next choice selection. The cancel branch is +# inherited from the last branch to not have cancel disabled. +# true => append; false => do not append. Default: true +# :scrolling - Whether to scroll once :max_lines is exceeded or to pause and +# start a new page. true => scroll; false => new page. Default: true +# :scroll_speed - If :scrolling is true, this is how many pixels it moves per +# frame when scrolling text. Default: 2 +# :scroll_show_arrows - If :scrolling is true, this determines whether or not +# to show the arrows when the contents grow. true => show; false => do +# not show. Default: true +# :scroll_autopause - If :scrolling is true, this determines whether or not to +# insert a pause before it scrolls to the next line. Default: false +# :scroll_review - If :scrolling is true, this option is whether or not the +# player has the option to review the message that has just finished by +# pressing up and down. true => yes; false => no. Default: true +# :scroll_by_page - Whether to scroll by whole page or by line. Default: false +# :paragraph_format - When this is true, the manual line breaks are ignored and +# it will draw as many characters as will fit on each line. You can still +# force a line break with the \lb code though. Default: false +# :justified_text - If :paragraph_format is true, then this option will make +# the spacing of a line exactly such that it takes up the entire width of +# the message window. Default: false +# :letter_sound - When this is true, a sound will be played upon drawing +# letters. Default: false +# :letter_se - If :letter_sound is true, this is the SE that is played. It is +# set in the format ["filename", volume, pitch]. Default: ["Open1", 40] +# :letters_per_se - If :letter_sound is true, this is the frequency that the +# SE is played. The SE will play every time this number of letters is +# drawn. Default: 3 +# :random_pitch - If :letter_sound is true, this is the range the pitch of the +# SE will vary within. It can be used to mimic the pitch variations of +# a human voice. It is a range in the format x..y. If x is equal to y, +# then the pitch won't vary. If set to an integer, it will take that as +# the variance from the regular pitch of the SE. Default: 100..100 +# :speech_tag_index - This refers to which speech tag to use when showing a +# message window above or below an event or player. -1 => no tag, while +# anything greater refers to the graphic of the corresponding index in +# :speech_tag_graphics. Default: -1 +# :speech_tag_graphics - This is an array containing the names of all speech +# tag graphics in the System folder of Graphics. Which one is used when +# showing a message depends on the value of :speech_tag_index. +# Default: ["Speech Tag 1", "Speech Tag 2", "Thought Tag 1"] +# :start_sound - If true, a sound will be played when a message starts. +# Default: false +# :start_se - If :start_sound is true, this is the SE that is played. It is +# set in the format ["filename", volume, pitch]. Default: ["Chime2"] +# :finish_sound - If true, a sound will be played when a message finishes. +# Default: false +# :finish_se - If :finish_sound is true, this is the SE that is played. It is +# set in the format ["filename", volume, pitch]. Default: ["Chime1"] +# :pause_sound - If true, a sound will be played when a message pauses. +# Default: false +# :pause_se - If :pause_sound is true, this is the SE that is played. It is +# set in the format ["filename", volume, pitch]. Default: ["Decision2"] +# :terminate_sound - If true, a sound will be played when a message closes. +# Default: false +# :terminate_se - If :terminate_sound is true, this is the SE that is played. It is +# set in the format ["filename", volume, pitch]. Default: ["Cancel"] +# :move_when_visible - When this is true, the player will still be able to move +# when the message window is displaying text. It is recommended that you +# turn scroll review off when using this feature though, as it looks +# dumb. Also, whenever a choice starts, it takes precedence and the +# player will not be able to move even if this is true. Default: false +# :graphic_novel - When this is true, it means the player can press the +# :hide_button and everything relating to the message window becomes +# invisible and stops updating until the player releases the button. It +# is intended for use where a player might want to see the whole screen +# and not have things hidden by the ATS windows. Default: false +# :hide_button - If :graphic_novel is true, this is the button that hides the +# ATS windows when pressed. Default: Input::F5 +# :gn_press_or_toggle - If :graphic_novel is true, this determines whether the +# player has to hold down the :hide_button or whether it acts as a toggle. +# true => press; false => toggle. Default: true +# :filters - This is a hash holding user-defined replacements for special +# codes. This is the hash that is accessed when using the \f[x] message +# code and is useful, for instance, if you have a character choose their +# sex at the start of the game. Then you can set the filters to the +# appropriate pronoun and use that and not have to have separate text +# boxes just to differentiate between when people say he or her. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :message_x - This is the x coordinate of the message window. If it is set to +# -1, then it will be centred. If not, it will be directly set to that +# coordinate of the screen. Default: -1 +# :message_y - This is the y coordinate of the message window. If it is set to +# -1, then it will abide by the normal way of setting position through +# the top, middle, bottom option in the text window. If it is not -1, +# then it will be directly set to that coordinate. Default: -1 +# :wlh - the line height of lines in the message window. Default: 24 +# :battle_wlh - the line height of lines in the message window during battle. +# This should always be 24 unless you have a good reason. Default: 24 +# :do_not_obscure - When using default positioning, this will choose a +# different position setting if the current one is over any of the +# characters included in :obscure_characters +# :obscure_characters - If :do_not_obscure is true and the message is being set +# using default positioning, then the message box will be choose override +# the chosen position if the chosen position obscures one or more of the +# characters in this array and another position would obscure less. The +# player is identified by 0 and any number greater than that refers to +# the event with that ID. When setting up the default, it is recommended +# that you only include the player and set others only on a map by map +# basis. Default: [0] +# :obscure_buffer - this is how much space from the bottom of the character you +# want to avoid obscuring. It should be set to the height of the tallest +# character you want to avoid obscuring. Default: 32 +# :fit_window_to_text - If this is true, then the window will be made to be +# just wide enough to contain the longest line and just tall enough to +# contain either all the lines up to :max_lines. Default: false +# :message_width - This is how many pixels wide the message window is. +# Default: 544 +# :message_height - This is how many pixels tall the message window is. +# Default: 128 +# :message_opacity - This is the opacity of the window. Default: 255 +# :message_backopacity - This is the opacity of the background of the +# windowskin. Default: 200 +# :message_windowskin - This is the windowskin that this window uses. It must +# be a graphic located in the System folder. Default: "Window" +# :message_fontcolour - This is the default colour of the font that is used +# everytime a new page is made. It can either refer to the windowskin +# palette (0-31) or be [red, green, blue] array. Default: 0 +# :message_fontname - This is the default font of the window. It can either +# refer to a single font or to a prioritized array of fonts, where the +# first one is used unless that font is not installed, and so on. +# Default: ["Verdana", "Arial", "Courier New"] +# :message_fontsize - This is the default size of the font. Default: 20 +# :message_fontalpha - This is the default opacity of the text. Default: 255 +# :message_dim - The graphic to use when selecting the "Dim" option. +# Default: "MessageBack" +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :face_x - The x coordinate of the face box. If set to -1, the x-coordinate +# will be automatically positioned. If not, it will be directly set to +# this value. Default: -1 +# :face_y - The y coordinate of the face box. If set to -1, the y-coordinate +# will be automatically positioned. If not, it will be directly set to +# this value. Default: -1 +# :face_z - If this is >0, it will show up above the message window. If less +# than 0, it will show up below the message window. Default: 10 +# :face_side - When :face_x is -1, this determines which side of the message +# box the face shows up on. true => Left; false => Right. Default: true +# :face_offset_x - When :choice_x is -1, this is added to the x position. +# Default: 0 +# :face_offset_y - When :choice_y is -1, this is added to the y position. +# Default: 0 +# :face_width - This determines the width of the face. When 0, it will show the +# whole width of the face, even if using single face graphics. If you want +# to show only a portion of the face, then set it directly. Default: 0 +# :face_height - This determines the height of the face. When 0, it will show +# the whole height of the face, even if using single face graphics. If +# you want to show only part of the face, then set it directly. Default: 0 +# :face_mirror - When true, the face will be drawn mirrored. Default: false +# :face_opacity - This is the opacity of the face. Default: 255 +# :face_blend_type - This is the blend type of the face sprites. 0 => normal; +# 1 => addition; 2 => subtraction. Default: 0 +# :face_fadein - When this is true, the face will fadein when starting a +# message instead of just automatically appearing. Default: false +# :face_fade_speed - If :face_fadein is true, this is how much the opacity will +# change every frame. Default: 10 +# :face_scroll_x - Option to scroll the face in horizontally. Default: false +# :face_scroll_y - Option to scroll the face in vertically. Default: false +# :face_scroll_speed - If either :face_scroll_x or :face_scroll_y are true, +# this is the number of pixels per frame it scrolls in at. +# :animate_faces - When this is true, specially labelled face files will +# animate. Face files that have ![x] in the name will take the first x +# faces in the file and animate between them. Alternatively, it can take +# the face of the same index from separate face files that are sequenced +# by suffixes _1, _2, etc... Default: true +# :letters_per_face - when :animate_faces is true, this is how many letters to +# draw between rotating the faces. +# :face_window - If true, the face will be framed by a window. Default: false +# :face_window_opacity - If :face_window is true, this is the opacity of that +# window. Default: 255 +# :face_windowskin - If :face_window is true, this is the windowskin it uses. +# Default: "Window" +# :face_border_size - If :face_window is true, this is the size of the border. +# Default: 6 +# :face_dim - If using :face_window and dim, the sprite to stretch as a +# background for the face. Default: "MessageBack" +# :face_use_dim - The setting for using dim. If 0, it will never use dim. If 1, +# it will use the dim sprite if :face_window is true. If 2, it will use +# dim only when the message window is also using dim. Default: 0 +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :choice_text - This is a bracket for any choice text. It is useful if, for +# instance, you want particular codes to apply to every choice in a +# branch. You must include %s in the string, and that is where the actual +# choice text will show up, bracketed by whatever other codes you put in +# Default: " %s" (meaning every choice will be indented by four spaces) +# :disabled_choice_text - Same as above, except it only applies to choices that +# are disabled and is applied after :choice_text is. It is useful if you +# want to differentiate between disabled choices and non-disabled ones. +# Unlike :choice_text, %s doesn't have to be included and in that case all +# disabled choices would simply be replaced by this text. +# Default: "\\FA[128]%s\\FA[255]" (meaning disabled choices will be drawn +# at 128 opacity). +# :choicebox_text - The same as :choice_text, but this is what is applied when +# using a choicebox instead of :choice_text. Default: "%s" (no change) +# :choice_window - Whether or not choices will show up in a separate window. +# Default: false +# :choice_x - The x coordinate of the choice window. When this is -1, the x +# position will be set automatically in reference to the position of the +# message window (end of the side opposite the face). When anything else, +# the x position is directly set to that coordinate. Default: -1 +# :choice_y - The y coordinate of the choice window. Whwn this is -1, the y +# position is set automatically directly above the message window or +# below, if it goes off screen when placed above. Default: -1 +# :choice_offset_x - When :choice_x is -1, this is added to the auto x position. +# Default: -16 +# :choice_offset_y - When :choice_y is -1, this is added to the auto y position. +# Default: 16 +# :choice_width - This is the width of the choice window. When -1, it will set +# it to as wide as necessary to accomodate the longest line, up to a +# maximum of the screen width. Default: 192 +# :choice_height - This is the height of the choice window. When -1, it is set +# to the number of rows necessary to draw all choices, up to :max_lines. +# :column_max - When using :choice_window, the number of columns. Default: 1 +# :row_max - When :choice_height is -1, the max number of rows. Default: 4 +# :choice_spacing - When :column_max > 1, this is how much empty space is left +# between options on the same row. Default: 20 +# :choice_opacity - This is the opacity of the choice window. Default: 255 +# :choice_backopacity - This is the opacity of the background of the choice +# window. Default: 200 +# :choice_windowskin - This is the name of the System file to use as the +# windowskin for the choice window. Default: "Window" +# :choice_fontcolour - The default colour of text in the choice window. It can +# be from the windowskin palette or a [red, green, blue] array. Default: 0 +# :choice_fontname - The font for the choice window. If an array, it will take +# the first font in the array that the player has installed. +# Default: ["Verdana", "Arial", "Courier New"] +# :choice_fontsize - Default size of the font in the choice window. Default: 20 +# :choice_wlh - The vertical space of each row of the choice window. Default: 24 +# :choice_dim - If using :choice_window and dim, the sprite to stretch as a +# background for the choice branch. Default: "MessageBack" +# :choice_use_dim - The setting for using dim. If 0, it will never use dim. If +# 1, it will use the dim sprite if :choice_window is true. If 2, it will +# use dim only when the message window is also using dim. Default: 2 +# :choice_on_line - Whether to show the choice box adjacent to the message +# window or not. Doesn't apply if :choice_width is not directly set or if +# :fit_window_to_text is on and larger than :choice_width. Default: false +# :choice_opposite_face - When true, the side the choice is shown on is the +# opposite side the face is on. And vice versa. Default: true +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :choicehelp_x - The x coordinate of the choicehelp window. When this is -1, +# the x position is centred. Default: -1 +# :choicehelp_y - The y coordinate of the choicehelp window. Whwn this is -1, +# the y position is at the top of the screen unless it overlaps with the +# message window, in which case it goes to the bottom. Default: -1 +# :choicehelp_width - This is the width of the choicehelp window. When -1, it +# will set it to the width of the longest line, up to a maximum of the +# screen width. Default: -1 +# :choicehelp_height - This is the height of the choicehelp window. When -1, it +# is set to accomodate the greatest number of lines that any choice option +# requires: Default: -1 +# :choicehelp_center - Whether or not to centre the text vertically when the +# number of lines is smaller than the height of the window. Default: true +# :choicehelp_opacity - This is the opacity of the choicehelp window. +# Default: 255 +# :choicehelp_backopacity - This is the opacity of the background of the +# choicehelp window. Default: 200 +# :choicehelp_windowskin - This is the name of the System file to use as the +# windowskin for the choicehelp window. Default: "Window" +# :choicehelp_fontcolour - This is the default colour of text in the choicehelp +# window. It can either be from the windowskin palette or a +# [red, green, blue, alpha] array. Default: 0 +# :choicehelp_fontname - This is either a string or array containing the name +# of the font used in the choicehelp window. If an array, it will take +# the first font in the array that the player has installed. +# Default: ["Verdana", "Arial", "Courier New"] +# :choicehelp_fontsize - This is the default size of the font in the choicehelp +# window. Default: 20 +# :choicehelp_wlh - This is the vertical space required for each line of the +# choicehelp window. Default: 24 +# :choicehelp_dim - If using :choicehelp_window and dim, the sprite to stretch +# as a background for the choicehelp branch. Default: "MessageBack" +# :choicehelp_use_dim - The setting for using dim. If 0, it will never use dim. +# If 1, it will use the dim sprite if :choicehelp_window is true. If 2, +# it will use dim only when the message window is also using dim. +# Default: 2 +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :name_x - The x coordinate of the name window. When this is -1, the x +# coordinate is automatically placed on the same side the face would be +# if automatically positioned. Default: -1 +# :name_y - The y coordinate of the name window. When this is -1, the y +# coordinate is just above the message window, unless it goes off the +# screen, in which case it is placed just below. Default: -1 +# :name_offset_x - When :name_x is -1, this is added to the x position. +# Default: 16 +# :name_offset_y - When :name_y is -1, this is added to the y position. +# Default: 16 +# :name_opacity - This is the opacity of the name window. Default: 255 +# :name_backopacity - This is the opacity of the background of the name window. +# Default: 200 +# :name_windowskin - This is the name of the System file to use as the +# windowskin for the name window. Default: "Window" +# :name_border_size - The size of the windowskin border around the name. +# Default: 8 +# :name_wlh - This is the vertical space required for each line of the name +# window. Default: 24 +# :name_fontcolour - This is the default colour of text in the name window. It +# can either be from the windowskin palette or a [red, green, blue] +# array. Default: 0 +# :name_fontname - This is either a string or array containing the name of the +# font used in the name window. If an array, it will take the first font +# in the array that the player has installed. +# Default: ["Verdana", "Arial", "Courier New"] +# :name_fontsize - This is the default size of the font in the name window. +# Default: 20 +# :name_dim - If using dim, the sprite to stretch as a background for the name +# box. Default: "MessageBack" +# :name_use_dim - The setting for using dim. If 0, it will never use dim. +# If 1, it will use the dim sprite. If 2, it will use dim only when the +# message window is also using dim. Default: 2 +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :word_x - The x coordinate of the word window. When this is -1, the word +# window is centred, or to the left of the gold window if the gold window +# is visible and :word_y is -1. If not -1, it is directly set to that +# coordinate. Default: -1 +# :word_y - The y coordinate of the word window. When this is -1, the y +# coordinate is at the same position as the gold window, unless the gold +# window is visible and the word window is too wide to fit to the left of +# it, in which case it is directly above or below the gold window, +# depending on where it fits. If not -1, it is directly set to that +# coordinate. Default: -1 +# :word_width - The width of the word window. When -1, it is set to accomodate +# the longest line. Default: 160 +# :word_height - The height of the word window. When -1, it will be set to +# accomodate the number of lines. Default: -1 +# :word_opacity - This is the opacity of the word window. Default: 255 +# :word_backopacity - This is the opacity of the background of the word window. +# Default: 200 +# :word_windowskin - This is the word of the System file to use as the +# windowskin for the word window. Default: "Window" +# :word_wlh - This is the vertical space required for each line of the word +# window. Default: 24 +# :word_fontcolour - This is the default colour of text in the word window. It +# can either be from the windowskin palette or a [red, green, blue] +# array. Default: 0 +# :word_fontname - This is either a string or array containing the name of the +# font used in the word window. If an array, it will take the first font +# in the array that the player has installed. +# Default: ["Verdana", "Arial", "Courier New"] +# :word_fontsize - This is the default size of the font in the word window. +# Default: 20 +# :word_dim - If using dim, the sprite to stretch as a background for the word +# box. Default: "MessageBack" +# :word_use_dim - The setting for using dim. If 0, it will never use dim. +# If 1, it will use the dim sprite. If 2, it will use dim only when the +# message window is also using dim. Default: 2 +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~ats_next only codes~ +# The following codes can only be set on a message by message basis with +# ats_next or $game_message. ats_all and $game_ats does not work for these. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# :do_not_refresh - When this is set to true, the contents of the message +# window will not refresh until it is turned off. This means that text +# messages will be appended to all the previous messages even if there +# are other events in between, such as a Show Picture or Control +# Variables switch and so on... Note that you will not be able to change +# the face either without forcibly setting a new page. To turn it off, +# you need to use a ats_next (:do_not_refresh, false) right before the +# last message that you want appended. I do not recommend doing anything +# too fancy with this; the idea is to permit you to do small things, like +# Showing a picture, turning a switch on or off or doing some stuff with +# conditional branches in between drawing letters. It's not designed or +# tested for much of anything else. +# :character - This is the same idea as the \oc, \uc, etc... character +# positioning codes. This allows you to place it prior to opening the +# window however. Set this value to the ID of the character you want to +# position it in reference to (0 => Player, >1 => Event with that ID. +# :char_ref - This is how you want to place it. 0 => Over; 1 => Left; +# 2 => Under; 3 => Right; 4 => Over if fits, otherwise under. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# You can set default values for all properties starting at line 766. +#============================================================================== + +#============================================================================== +# ** Game_ATS +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This class holds all of the default data for the ATS +#============================================================================== + +class Game_ATS + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # EDITABLE REGION + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + MAX_LINES = 4 # See line 376. + MESSAGE_SPEED = 0 # See line 378. + SKIP_DISABLED = false # See line 380. + APPEND_TEXT = false # See line 381. + APPEND_CHOICE = true # See line 383. + SCROLLING = true # See line 387. + SCROLL_SPEED = 2 # See line 389. + SCROLL_SHOW_ARROWS = true # See line 391. + SCROLL_AUTOPAUSE = false # See line 394. + SCROLL_REVIEW = true # See line 396. + SCROLL_BY_PAGE = false # See line 399. + PARAGRAPH_FORMAT = true # See line 400. + JUSTIFIED_TEXT = false # See line 403. + LETTER_SOUND = false # See line 406. + LETTER_SE = "Open1", 40 # See line 408. + LETTERS_PER_SE = 3 # See line 410. + RANDOM_PITCH = 100..100 # See line 413. + SPEECH_TAG_INDEX = -1 # See line 418. + # SPEECH_TAG_GRAPHICS # See line 422. + SPEECH_TAG_GRAPHICS = ["Speech Tag 1", "Speech Tag 2", "Thought Tag 1"] + START_SOUND = false # See line 426. + START_SE = "Chime2" # See line 428. + FINISH_SOUND = false # See line 430. + FINISH_SE = "Chime1" # See line 432. + PAUSE_SOUND = false # See line 434. + PAUSE_SE = "Decision2" # See line 436. + TERMINATE_SOUND = false # See line 438. + TERMINATE_SE = "Cancel" # See line 440. + MOVE_WHEN_VISIBLE = false # See line 442. + GRAPHIC_NOVEL = false # See line 447. + HIDE_BUTTON = Input::F5 # See line 452. + GN_PRESS_OR_TOGGLE = true # See line 454. + FILTERS = { # See line 457. + 'ATS' => '\c[1]Advanced Text System\c[0], Version 3.0', + 0 => 'Numbered filters work too', + } + FILTERS.default = "" # <- Do not touch + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + MESSAGE_X = -1 # See line 464. + MESSAGE_Y = -1 # See line 467. + WLH = 24 # See line 471. + BATTLE_WLH = 24 # See line 472. + DO_NOT_OBSCURE = false # See line 474. + OBSCURE_CHARACTERS = [0] # See line 477. + OBSCURE_BUFFER = 32 # See line 485. + FIT_WINDOW_TO_TEXT = false # See line 488. + MESSAGE_WIDTH = 544 # See line 491. + MESSAGE_HEIGHT = 128 # See line 493. + MESSAGE_OPACITY = 255 # See line 495. + MESSAGE_BACKOPACITY = 200 # See line 496. + MESSAGE_WINDOWSKIN = "Window" # See line 498. + MESSAGE_FONTCOLOUR = 0 # See line 500. + MESSAGE_FONTNAME = ["Verdana", "Arial", "Courier New"] # See line 503. + MESSAGE_FONTSIZE = 20 # See line 507. + MESSAGE_FONTALPHA = 255 # See line 508. + MESSAGE_DIM = "MessageBack" # See line 509. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FACE_X = -1 # See line 512. + FACE_Y = -1 # See line 515. + FACE_Z = 10 # See line 518. + FACE_SIDE = true # See line 520. + FACE_OFFSET_X = 0 # See line 522. + FACE_OFFSET_Y = 0 # See line 524. + FACE_WIDTH = 0 # See line 526. + FACE_HEIGHT = 0 # See line 529. + FACE_MIRROR = false # See line 532. + FACE_OPACITY = 255 # See line 533. + FACE_BLEND_TYPE = 0 # See line 534. + FACE_FADEIN = false # See line 536. + FACE_FADE_SPEED = 10 # See line 538. + FACE_SCROLL_X = false # See line 540. + FACE_SCROLL_Y = false # See line 541. + FACE_SCROLL_SPEED = 12 # See line 542. + ANIMATE_FACES = true # See line 544. + LETTERS_PER_FACE = 6 # See line 545. + FACE_WINDOW = false # See line 551. + FACE_WINDOW_OPACITY = 255 # See line 552. + FACE_WINDOWSKIN = "Window" # See line 554. + FACE_BORDER_SIZE = 6 # See line 556. + FACE_DIM = "MessageBack" # See line 558. + FACE_USE_DIM = 0 # See line 560. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CHOICE_TEXT = " %s" # See line 564. + DISABLED_CHOICE_TEXT = "\\FA[128]%s\\FA[255]" # See line 569. + CHOICEBOX_TEXT = "%s" # See line 576. + CHOICE_WINDOW = false # See line 578. + CHOICE_X = -1 # See line 580. + CHOICE_Y = -1 # See line 584. + CHOICE_OFFSET_X = -16 # See line 587. + CHOICE_OFFSET_Y = 16 # See line 589. + CHOICE_WIDTH = 192 # See line 591. + CHOICE_HEIGHT = -1 # See line 594. + COLUMN_MAX = 1 # See line 596. + ROW_MAX = 4 # See line 597. + CHOICE_SPACING = 20 # See line 598. + CHOICE_OPACITY = 255 # See line 600. + CHOICE_BACKOPACITY = 200 # See line 601. + CHOICE_WINDOWSKIN = "Window" # See line 603. + CHOICE_FONTCOLOUR = 0 # See line 605. + CHOICE_FONTNAME = ["Verdana", "Arial", "Courier New"] # See line 607. + CHOICE_FONTSIZE = 20 # See line 610. + CHOICE_WLH = -1 # See line 611. + CHOICE_DIM = "MessageBack" # See line 612. + CHOICE_USE_DIM = 2 # See line 614. + CHOICE_ON_LINE = false # See line 617. + CHOICE_OPPOSITE_FACE = true # See line 620. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CHOICEHELP_X = -1 # See line 623. + CHOICEHELP_Y = -1 # See line 625. + CHOICEHELP_WIDTH = 544 # See line 628. + CHOICEHELP_HEIGHT = -1 # See line 631. + CHOICEHELP_CENTER = true # See line 634. + CHOICEHELP_OPACITY = 255 # See line 636. + CHOICEHELP_BACKOPACITY = 200 # See line 638. + CHOICEHELP_WLH = -1 # See line 640. + CHOICEHELP_WINDOWSKIN = "Window" # See line 642. + CHOICEHELP_FONTCOLOUR = 0 # See line 645. + CHOICEHELP_FONTNAME = ["Verdana", "Arial", "Courier New"] # See line 649. + CHOICEHELP_FONTSIZE = 20 # See line 651. + CHOICEHELP_DIM = "MessageBack"# See line 653. + CHOICEHELP_USE_DIM = 2 # See line 655. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAME_X = -1 # See line 660. + NAME_Y = -1 # See line 663. + NAME_OFFSET_X = 16 # See line 666. + NAME_OFFSET_Y = 16 # See line 668. + NAME_OPACITY = 255 # See line 670. + NAME_BACKOPACITY = 200 # See line 671. + NAME_WINDOWSKIN = "Window" # See line 673. + NAME_BORDER_SIZE = 8 # See line 675. + NAME_WLH = -1 # See line 677. + NAME_FONTCOLOUR = 0 # See line 679. + NAME_FONTNAME = ["Verdana", "Arial", "Courier New"] # See line 682. + NAME_FONTSIZE = 20 # See line 686. + NAME_DIM = "MessageBack" # See line 688. + NAME_USE_DIM = 2 # See line 690. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + WORD_X = -1 # See line 694. + WORD_Y = -1 # See line 698. + WORD_WIDTH = 160 # See line 704. + WORD_HEIGHT = -1 # See line 706. + WORD_OPACITY = 255 # See line 708. + WORD_BACKOPACITY = 200 # See line 709. + WORD_WINDOWSKIN = "Window" # See line 711. + WORD_WLH = -1 # See line 713. + WORD_FONTCOLOUR = 0 # See line 715. + WORD_FONTNAME = ["Verdana", "Arial", "Courier New"] # See line 718. + WORD_FONTSIZE = 20 # See line 722. + WORD_DIM = "MessageBack" # See line 724. + WORD_USE_DIM = 2 # See line 726. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # END EDITABLE REGION + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ATS_2 = false + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # For each constant, create accessor + for name in self.class.constants + # Run the script + attr_accessor name.downcase.to_sym + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize + reset + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Reset + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def reset + constants = Game_ATS.constants + constants.each do |name| + method_name = "#{name.downcase}=" # Build the setter method name + value = Game_ATS.const_get(name) # Get the constant value from Game_ATS + self.send(method_name.to_sym, value) # Send the method call dynamically + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Sound Effect + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_sound_effect(settings) + return settings if settings.is_a?(RPG::SE) + + settings = [settings] if settings.is_a?(String) + + settings[1] = 80 if !settings[1] # Ensure the second element is set + + return RPG::SE.new(*settings) # Call RPG::SE.new with the unpacked arguments + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Overwrite SE methods * Thanks Zeriab + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for method in ["letter", "terminate", "pause", "start", "finish"] + # Run the script + eval("def #{method}_se= (*args); @#{method}_se = set_sound_effect (*args); end") + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Random Pitch + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def random_pitch= (val) + @random_pitch = val.is_a? (Integer) ? self.letter_se.pitch..val : val + end +end + +#============================================================================== +# ** Game_Message +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# new attr_accessor - all Game_ATS accessors; char_ref; character; +# appending_text; choices; disabled_choices; help_choices; skip_choices; +# override_run +# aliased methods - initialize; clear; busy +# new methods - convert_special_characters; perform_substitution; +# perform_conversion; play_se; random_pitch= +#============================================================================== + +class Game_Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # For each ATS constant, create accessor + for name in (Game_ATS.constants) do attr_accessor name.downcase.to_sym end + # Non Game_ATS variables + attr_accessor :char_ref + attr_accessor :character + attr_accessor :appending_text + attr_accessor :choices + attr_accessor :disabled_choices + attr_accessor :skip_choices + attr_accessor :help_choices + attr_accessor :override_run + attr_accessor :highlight + attr_accessor :underline + attr_accessor :do_not_refresh + attr_accessor :do_not_start + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malgb_advtx_inliz_2fk9 initialize unless $@ + + def initialize(*args) + @do_not_refresh = false + # malg_ats3_clr_msg_9lo1(*args) # Call your method without a space before `(*args)` + malgb_advtx_inliz_2fk9(*args) # Call the original `initialize` method (aliased) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Clear + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malg_ats3_clr_msg_9lo1 clear unless $@ + def clear (*args) + @do_not_start = @do_not_refresh + @texts = [] + @choices = [] + @disabled_choices = [] + @skip_choices = [] + @help_choices = [] + @choice_start = 99 + @choice_max = 0 + @choice_cancel_type = 0 + @choice_proc = nil + @ignored_codes = [] + return if @do_not_start + malg_ats3_clr_msg_9lo1 (*args) # Run Original Method + @alignment = 0 + @appending_text = false + @character = -1 + @char_ref = 0 + @override_run = false + @highlight = -1 + @underline = false + # Set ATS variables + (Game_ATS.constants).each { |name| + self.send ("#{name.downcase}=".to_sym, $game_ats.send (name.downcase.to_sym)) + } + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Busy? + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malbr_busy_ats3_8ik2 busy unless $@ + def busy (*args) + return false if @appending_text + return malbr_busy_ats3_8ik2 (*args) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Convert Special Characters + # text : the text to convert + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def convert_special_characters (text) + return if text == nil + @ignored_codes.clear + # Remove Ignore code strings + text.gsub! (/\\\$(.*?)\/\$/i) { + @ignored_codes.push ($1.to_s) + "\x0a<#{@ignored_codes.size - 1}>" + } + # Get substitutions + text = perform_substitution (text) + text = perform_conversion (text) + text.gsub! (/\\\\/) { "\\" } + text.gsub! (/\x0a<(\d+)>/) { @ignored_codes[$1.to_i] } # Recover Protected strings + return text + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Convert Substitution Codes + # text : the text to convert + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def perform_substitution (text) + # Switch conditional text + text.gsub! (/\\V\[(\d+)\]/i) { $game_variables[$1.to_i] } # Variable + text.gsub! (/\\PID\[(\d+)\]/i) { $game_party.members[($1.to_i % $game_party.members.size)].id } # Party Member X + text.gsub! (/\\P\[(\d+)\]/i) { $game_party.members[($1.to_i % $game_party.members.size)].id } # Party Member X + text.gsub! (/\\V\[(\d+)\]/i) { $game_variables[$1.to_i] } # Variable + # FILTERS + text.gsub!(/\\F\[["'](.+?)["']\]/i) { Game_ATS::FILTERS[$1.to_s] } + text.gsub!(/\\F\[(.+?)\]/i) { Game_ATS::FILTERS[$1.to_i] } + # Party ID to Actor ID + text.gsub!(/\\N\[([0-9]+)\]/i) { $game_actors[$1.to_i].name rescue ""} # Actor Name + # New Codes + text.gsub! (/\\AC\[(\d+)\]/i) { $game_actors[$1.to_i].class.name rescue "" } # Actor Class + text.gsub! (/\\VOCAB\[(\w+)\]/i) { Vocab.send ($1.downcase) rescue "" } + # Actor, Item, Weapon, Armor, Skill Stats + data_arrays = [$game_actors, $data_items, $data_weapons, $data_armors, + $data_skills, $data_states, $data_enemies] + regexp_array = ["ACTOR_", "I_", "W_", "A_", "S_", "T_", "ENEMY_"] + for i in 0...regexp_array.size + regexp = regexp_array[i] + data = data_arrays[i] + if i != 0 + text.gsub! (/\\#{regexp}N\[(\d+)\]/i) { + id = $1.to_i + data[id].note[/\\MSG\[(.+?)\]MSG\//i].nil? ? data[id].note : $1.to_s.gsub (/\n/) { "" } # Remove \n + } + end + # Item Stats + text.gsub! (/\\#{regexp}([^\[]+?)\[(\d+)\]/i) { |match| data[$2.to_i].send ($1.downcase).to_s rescue match } + end + text.gsub! (/\\MAP\[(\d+)\]/i) { (load_data ("Data/MapInfos.rvdata"))[$1.to_i].name rescue "" } + text.gsub! (/\\MAP/i) { (load_data ("Data/MapInfos.rvdata"))[$game_map.map_id].name rescue "" } + text.gsub! (/\\NL\[(\d+)\]/i){ $data_system.elements[$1.to_i] rescue "" } + text.gsub! (/\\NC\[(\d+)\]/i) { $data_classes[$1.to_i].name rescue "" } # Class Name + text.gsub! (/\\NE\[(\d+)\]/i) { $game_map.events[$1.to_i].name rescue "" } # Event Name + text.gsub! (/\\NM\[(\d+)\]/i) { $data_enemies[$1.to_i].name rescue "" } # Monster Name + text.gsub! (/\\NI\[(\d+)\]/i) { $data_items[$1.to_i].name rescue "" } # Item Name + text.gsub! (/\\NW\[(\d+)\]/i) { $data_weapons[$1.to_i].name rescue "" } # Weapon Name + text.gsub! (/\\NA\[(\d+)\]/i) { $data_armors[$1.to_i].name rescue "" } # Armor Name + text.gsub! (/\\NS\[(\d+)\]/i) { $data_skills[$1.to_i].name rescue "" } # Skill Name + text.gsub! (/\\NT\[(\d+)\]/i) { $data_states[$1.to_i].name rescue "" } # State Name + text.gsub! (/\\NP\[(\d+)\]/i) { $game_party.members[($1.to_i % $game_party.members.size)].name } # Party Name + text.gsub! (/\\NV\[(\d+)\]/i) { $data_system.variables[$1.to_i] rescue "" } # Variable Name + text.gsub! (/\\NSW\[(\d+)\]/i){ $data_system.switches[$1.to_i] rescue "" } # Switch Name + text.gsub! (/\\PI\[(\d+)\]/i) { $data_items[$1.to_i].price.to_s rescue "" } # Item Price + text.gsub! (/\\PW\[(\d+)\]/i) { $data_weapons[$1.to_i].price.to_s rescue "" } # Weapon Price + text.gsub! (/\\PA\[(\d+)\]/i) { $data_armors[$1.to_i].price.to_s rescue "" } # Armor Price + text.gsub! (/\\DI\[(\d+)\]/i) { $data_items[$1.to_i].description rescue "" } # Item Description + text.gsub! (/\\DW\[(\d+)\]/i) { $data_weapons[$1.to_i].description rescue "" } # Weapon Description + text.gsub! (/\\DA\[(\d+)\]/i) { $data_armors[$1.to_i].description rescue "" } # Armor Description + text.gsub! (/\\DS\[(\d+)\]/i) { $data_skills[$1.to_i].description rescue "" } # Skill Description + text.gsub! (/\\I#\[(\d+)\]/i) { $game_party.item_number ($data_items[$1.to_i]) } # Item Number + text.gsub! (/\\W#\[(\d+)\]/i) { $game_party.item_number ($data_weapons[$1.to_i]) } # Weapon Number + text.gsub! (/\\A#\[(\d+)\]/i) { $game_party.item_number ($data_armors[$1.to_i]) } # Armor Number + text.gsub! (/\\S\!<(\d+),(.+?)>/i) { $game_switches[$1.to_i] ? "" : $2.to_s } + text.gsub! (/\\S<(\d+),(.+?)>/i) { $game_switches[$1.to_i] ? $2.to_s : "" } + text.gsub! (/\\#\{(.+?)\}#/im) { (eval ($1.to_s)).to_s rescue "" } + return text.sub! (/\\RESUB/i, "") != nil ? perform_substitution (text) : text + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Perform Conversion + # text : the text to convert + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def perform_conversion (text) + text.gsub! (/\\C\[(\d+)\]/i) { "\x01<#{$1}>" } # Colour + text.gsub! (/\\C\[#([\dABCDEF]{6,6})\]/i) { "\x01<##{$1}>"} # Colour Hex + text.gsub! (/\\G/i) { "\x02" } # Gold Window + text.gsub! (/\\\./) { "\x03<15>" } # Wait 15 frames + text.gsub! (/\\\|/) { "\x03<60>" } # Wait 60 frames + text.gsub! (/\\W\[(\d+)\]/i) { "\x03<#{$1}>" } # Wait x frames + text.gsub! (/\\!/) { "\x04" } # Wait for Input + text.gsub! (/\\@@/) { "\x05<0>" } # Show Fast ON + text.gsub! (/\/@@/) { "\x05<1>" } # Show Fast OFF + text.gsub! (/\\@/) { "\x06<0>" } # Show Line Fast ON + text.gsub! (/\/@/) { "\x06<1>" } # Show Line Fast OFF + text.gsub! (/\\\^/) { "\x07" } # Skip Pause + text.gsub! (/\\>/) { "\x08<-1>" } # Message speed- 1 + text.gsub! (/\\" } # Message speed + 1 + text.gsub! (/\\[Ss]\[(=?-?\d+)\]/i) { "\x08<#{$1}>" } # Message speed + x + text.gsub! (/\\%/) { "\xb0<0>" } # Enable Skip + text.gsub! (/\/%/) { "\xb0<1>" } # Disable Skip + # Animation and Balloons + text.gsub! (/\\ANI\[(\d+),\s*(\d+)\]/i) { "\x0b<#{$1},#{$2},0>" } # Animation + text.gsub! (/\\BLN\[(\d+),\s*(\d+)\]/i) { "\x0b<#{$1},#{$2},1>" } # Balloon + # Position to Character + text.gsub! (/\\OC\[(\d+)\]/i) { "\x0c<#{$1},0>" } # Over Character + text.gsub! (/\\LC\[(\d+)\]/i) { "\x0c<#{$1},1>" } # Left of Character + text.gsub! (/\\UC\[(\d+)\]/i) { "\x0c<#{$1},2>" } # Under Character + text.gsub! (/\\RC\[(\d+)\]/i) { "\x0c<#{$1},3>" } # Right of Character + text.gsub! (/\\E\[(\d+)\]/i) { "\x0c<#{$1},4>" } # Under if over doesn't work + text.gsub! (/\\SE\[(.+?)\]/i) { "\x1b<#{$1},SE>" } # Play Sound Effect + text.gsub! (/\\ME\[(.+?)\]/i) { "\x1b<#{$1},ME>" } # Play Musical Effect + # Show Icons + text.gsub! (/\\IC?O?N?\[(\d+)\]/i) { "\x0d<#{$1}>" } # Show Icon X + text.gsub! (/\\IIC?O?N?\[(\d+)\]/i) { "\x0d<#{$data_items[$1.to_i].icon_index}>" rescue "" } # Item Icon + text.gsub! (/\\WIC?O?N?\[(\d+)\]/i) { "\x0d<#{$data_weapons[$1.to_i].icon_index}>" rescue "" } # Weapon Icon + text.gsub! (/\\AIC?O?N?\[(\d+)\]/i) { "\x0d<#{$data_armors[$1.to_i].icon_index}>" rescue "" } # Armor Icon + text.gsub! (/\\SIC?O?N?\[(\d+)\]/i) { "\x0d<#{$data_skills[$1.to_i].icon_index}>" rescue "" } + text.gsub! (/\\TIC?O?N?\[(\d+)\]/i) { "\x0d<#{$data_states[$1.to_i].icon_index}>" rescue "" } + text.gsub! (/\\[Bb]/) { "\x0e<0>" } # Bold ON + text.gsub! (/\/[Bb]/) { "\x0e<1>" } # Bold OFF + text.gsub! (/\\[Ii]/) { "\x0f<0>" } # Italics ON + text.gsub! (/\/[Ii]/) { "\x0f<1>" } # Italics OFF + text.gsub! (/\\[Ss]/) { "\x10<0>" } # Shadow ON + text.gsub! (/\/[Ss]/) { "\x10<1>" } # Shadow OFF + text.gsub! (/\\[Uu]/) { "\x11<0>" } # Underline ON + text.gsub! (/\/[Uu]/) { "\x11<1>" } # Underline OFF + text.gsub! (/\\HL\[(-?\d+)\]/i) { "\x12<#{$1}>" } # Higlight X + text.gsub! (/\/HL/i) { "\x12<-1>" } # Highlight OFF + text.gsub! (/\\FN\[(.+?)\]/i) { "\x13<#{$1}>" } # Font Name + text.gsub! (/\\FS\[(\d+)\]/i) { "\x14<#{$1}>" } # Font Size + text.gsub! (/\\FA\[(\d+)\]/i) { "\x15<#{$1}>" } # Font Alpha + text.gsub! (/\\LB/i) { "\x16" } # Force Line Break + text.gsub! (/\\AF\[(\d+)\]/i) { "\x17<#{$1.to_s}>" } # Use Actor Face + text.gsub! (/\\LEFT/i) { "\x18<0>" } # Align Left + text.gsub! (/\\L/i) { "\x18<0>" } # Align Left + text.gsub! (/\\CENTRE/i) { "\x18<1>" } # Align Centre + text.gsub! (/\\CE?NTE?R/i) { "\x18<1>" } # Align Centre + text.gsub! (/\\C/i) { "\x18<1>" } # Align Centre + text.gsub! (/\\RI?GHT/i) { "\x18<2>" } # Align Right + text.gsub! (/\\R/i) { "\x18<2>" } # Align Right + text.gsub! (/\\PB/i) { "\x1d" } # Force Page Break + text.gsub! (/(\x1d\s*)\x00/i) { $1.to_s } # If force page, remove x00 code + text.gsub! (/\\T/i) { "\x09" } # Tab + text.gsub! (/\\X\[(\d+)\]/i) { "\xb1<#{$1}>" } # Content X + # Position Direct Setting + text.gsub! (/\\[PM]XY\[(-?\d+),\s*(-?\d+)\]/im) { "\x1f<0,#{$1.to_s},#{$2.to_s}>" } + # Face Direct Setting + text.gsub! (/\\FXY\[(-?\d+),\s*(-?\d+)\]/im) { "\x1f<1,#{$1.to_s},#{$2.to_s}>" } + # Name Direct Setting + text.gsub! (/\\NXY\[(-?\d+),\s*(-?\d+)\]/im) { "\x1f<2,#{$1.to_s},#{$2.to_s}>" } + text.gsub! (/\\#\!\{(.+?)\}#/im) { "\x7f{#{$1}#}" } + text.gsub! (/\\NB\[(.*?)\]/im) { "\x19{#{$1}}" } # Name Window + text.gsub! (/\/NB/i) { "\x19{}"} + text.gsub! (/\\NAME\[(.*?)\]/im) { "\x19{#{$1}}" } # Name Window + text.gsub! (/\\WB\[(.*?)\]/im) { "\x1a{#{$1}}" } # Word Box + text.gsub! (/\/WB/i) { "\x1a{}" } + return text + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Play SE + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def play_se (se) + # Avoid FileErrors + begin + se.play + rescue + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Choice Texts + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def choice_text= (string) + if string.is_a? (String) + @choice_text = convert_special_characters (string) + @choice_text += "%s" if @choice_text[/%s/] == nil + else + @choice_text = "%s" + end + end + def choicebox_text= (string) + if string.is_a? (String) + @choicebox_text = convert_special_characters (string) + @choicebox_text += "%s" if @choicebox_text[/%s/] == nil + else + @choicebox_text = "%s" + end + end + def disabled_choice_text= (string) + @disabled_choice_text = string.is_a? (String) ? convert_special_characters (string) : "%s" + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Random Pitch + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def random_pitch= (val) + @random_pitch = val.is_a? (Integer) ? self.letter_se.pitch..val : val + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Overwrite SE methods * Thanks Zeriab + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for method in ["letter", "terminate", "pause", "start", "finish"] + # Run the script + eval("def #{method}_se= (*args); @#{method}_se = $game_ats.set_sound_effect (*args); end") + end +end + +#============================================================================== +# ** Game_Event +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# new method - name +#============================================================================== + +class Game_Event + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Name + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def name + return @event.name + end +end + +#============================================================================== +# ** Game_Player +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# aliased methods - movable?; move_by_input +#============================================================================== + +class Game_Player + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Move By Input + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mabr_adtxs3_inpmv_6yw2 move_by_input unless $@ + def move_by_input (*args) + $game_message.override_run = $game_message.move_when_visible + mabr_adtxs3_inpmv_6yw2 (*args) # Run Original Method + $game_message.override_run = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Check if Player can Move + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malg_advtxt3_movble_5sa2 movable? unless $@ + def movable? (*args) + true_visible = $game_message.visible + $game_message.visible = false if $game_message.move_when_visible + check = malg_advtxt3_movble_5sa2 (*args) # Run Original Method + $game_message.visible = true_visible + return check + end +end + +#============================================================================== +# ** Game_Interpreter +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# overwritten methods - command_403; setup_choices; setup_num_input +# aliased methods - command_101; command_102; running? +# new methods - interpret_choices; choice_plus; choice_switch; choice_help; +# ats_next; ats_all +#============================================================================== + +class Game_Interpreter + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Show Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modalg_ats_apnd_txt_show_94b command_101 unless $@ + def command_101 (*args) + value = modalg_ats_apnd_txt_show_94b (*args) + if $game_message.append_text && @list[@index].code == 101 && @list[@index].parameters == @params + $game_message.appending_text = true + value = command_101 (*args) + $game_message.appending_text = false + end + return value + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Show Choices + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malgr_ats3_shwchc_4ac2 command_102 unless $@ + def command_102 (*args) + if @list[@index].indent > 1000 + @list[@index - 1].indent -= 1000 + @list[@index].indent -= 1000 + return command_skip + else + return malgr_ats3_shwchc_4ac2 (*args) # Run Original Method + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * When [Cancel] + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def command_403 + if @branch[@indent] == 999 # If canceling choice + @branch.delete(@indent) # Erase branching data + return true # Continue + else # If doesn't match condition + return command_skip # Command skip + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Setup Choices + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def setup_choices (params) + params = interpret_choices (Marshal.load (Marshal.dump (params))) + $game_message.choice_start = $game_message.texts.size + $game_message.choice_max = params[0].size + for s in params[0] + $game_message.choices.push(s) + end + $game_message.texts.push ("\x1c") if $game_message.texts.empty? + $game_message.choice_cancel_type = params[1] + $game_message.choice_proc = Proc.new { |n| @branch[@indent] = n } + @index += 1 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Number Input Setup + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def setup_num_input(params) + if $game_message.texts.size < $game_message.max_lines || $game_message.scrolling + $game_message.num_input_variable_id = params[0] + $game_message.num_input_digits_max = params[1] + @index += 1 + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Interpret Choices + # params : the parameters of the initial 102 command. + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def interpret_choices (params) + # Collect all next choices as well. + ind = @index + 1 + choice_id = 0 + params[1] = 1000 if params[1] == 5 + choice_text = $game_message.choice_window ? $game_message.choicebox_text : $game_message.choice_text + loop do + # Advance to next choice code + while (@list[ind].indent % 1000) > @indent + ind += 1 # Advance index + end + case @list[ind].code + when 402 + choice_s = @list[ind].parameters[1].dup + ind += 1 + # Choice+ processing. + choice_s, ind = choice_plus (choice_s, ind) + # SOFF and SON processing + choice_s, delete, disable = choice_switch (choice_s) + choice_s, help = choice_help (choice_s) + if delete || choice_s.empty? + @list[ind - 1].parameters[0] = -1 + params[0].delete_at (choice_id) + else + $game_message.help_choices[choice_id] = help + choice_s = sprintf (choice_text, choice_s) + if disable + choice_s = sprintf ($game_message.disabled_choice_text, choice_s) + $game_message.disabled_choices.push (choice_id) + end + # Choice Skip? + choice_s.gsub! (/\x1e/i) { $game_message.skip_choices.push (choice_id); "" } + @list[ind - 1].parameters[0] = choice_id + params[0][choice_id] = choice_s + choice_id += 1 + end + when 403 + ind += 1 + when 404 + if !$game_message.append_choice || @list[ind + 1].code != 102 + @index = ind if $game_message.skip_choices.size == params.size + $game_message.help_choices.clear if $game_message.help_choices.uniq.size == 1 && $game_message.help_choices[0].empty? + break + end + p2 = @list[ind + 1].parameters.dup + @list[ind].indent += 1000 if @list[ind].indent < 1000 + @list[ind + 1].indent += 1000 if @list[ind + 1].indent < 1000 + params[1] = p2[1] == 5 ? 1000 : p2[1] == 0 ? params[1] : params[0].size + p2[1] + params[0].push (*p2[0]) + ind += 2 + else + break + end + end + return params + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Running? + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modral_atsys3_runn_5tg3 running? unless $@ + def running? (*args) + return false if $game_message.override_run + return modral_atsys3_runn_5tg3 (*args) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Choice + Processing + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def choice_plus (choice_s, ind) + # Choice + Processing + if @list[ind].code == 108 # Comment + string = @list[ind].parameters[0].dup + ind2 = ind + 1 + while @list[ind2].code == 408 + string += @list[ind2].parameters[0] + ind2 += 1 + end + string.gsub (/\\\+{(.+?)}/im) { choice_s += $1.to_s; "" } + choice_s.gsub! (/\n/) { "" } # Remove \n + end + return choice_s, ind + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Choice Switch + # s : the choice string + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def choice_switch (s) + del = false + dis = false + s.gsub! (/\\SOFF\[(\d+)\]/i) { del = true if $game_switches[$1.to_i]; "" } + s.gsub! (/\\SON\[(\d+)\]/i) { del = true if !$game_switches[$1.to_i]; "" } + if !del + s.gsub! (/\\D\[(\d+)\]/i) { dis = true if !$game_switches[$1.to_i]; "" } + s.gsub! (/\\D!\[(\d+)\]/i) { dis = true if $game_switches[$1.to_i]; "" } + s.gsub! (/\\SKIP/i) { "\x1e" } + s.gsub! (/\\PB/i) { "" } # Delete any page breaks. + $game_message.convert_special_characters (s) + s.gsub! (/\x16/) { "\x00" } # Force Line Break + end + return s, del, dis + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Choice Help + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def choice_help (choice_s) + help_text = "" + choice_s.gsub! (/\x1a{(.+?)}/im) { help_text += $1.to_s; "" } + return choice_s, help_text + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Change ATS parameter for next message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def ats_next (parameter, *args) + $game_message.send ("#{parameter}=".to_sym, *args) unless args.empty? + return $game_message.send (parameter) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Change ATS + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def ats_all (parameter, *args) + $game_ats.send ("#{parameter}=".to_sym, *args) unless args.empty? + $game_message.send ("#{parameter}=".to_sym, *args) unless args.empty? + return $game_message.send (parameter) + end +end + +#============================================================================== +# ** P_Formatter_ATS +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# A special formatter class designed to accomodate ATS message codes +#============================================================================== + +class P_Formatter_ATS + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + attr_accessor :bitmap + attr_reader :force_break + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (font = nil) + @bitmap = Bitmap.new (1, 1) + @bitmap.font = font if font + @curly_args_codes = ["\x19", "\x1a", "\x7f"] + @args_codes = ["\x01", "\x03", "\x05", "\x06", "\x08", "\x0b", + "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", + "\x15", "\x17", "\x18", "\x1b", "\x1f", "\xb0", "\xb1"] + @no_args_codes = ["\x00", "\x02", "\x04", "\x07", "\x09", "\x16", "\x1c", + "\x1d", "\x1e"] + @force_break = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose Bitmap + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def dispose + @bitmap.dispose unless @bitmap.nil? || @bitmap.disposed? + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Format By Line + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def format_by_line (string, width) + @string = string + @max_width = width + @justify = $game_message.justified_text + return 0, 0, @justify if string.empty? + @line_width = 0 + @word_length = 0 + @w_draw_count = 0 + @l_draw_count = 0 + @curly_argument = 0 + @code_argument = 0 + # Set up + @line_space = 0 + @last_word = 0 + @break_loop = false + @last_space_counted = false + @force_break = false + i = 0 + while !@break_loop + if i >= @string.size + if @l_draw_count != 0 + @l_draw_count += 1 + @line_width += @bitmap.text_size (" ").width + end + @l_draw_count += @w_draw_count + @line_width += @word_length + next_line (i) + else + format_character (i) + i += 1 + end + end + return @line_space, @l_draw_count, @justify + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Format Character + # i : index of character to format, or the character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def format_character (i) + character = @string[i, 1] + if @curly_argument > 0 + @curly_argument -= 1 if character == "}" + elsif @code_argument > 0 + @code_argument -= 1 if character == ">" + elsif @curly_args_codes.include? (character) + extract_curly_args_code (character, i) + elsif @args_codes.include? (character) + extract_args_code (character, i) + elsif @no_args_codes.include? (character) + extract_no_args_code (character, i) + elsif character == " " + next_word (i) + return if @break_loop + @line_width += @bitmap.text_size (" ").width + @l_draw_count += 1 + @last_space_counted = true + else # Regular Character + @word_length += @bitmap.text_size(character).width + @w_draw_count += 1 + if i == @string.size - 1 && @line_width + @word_length > @max_width + next_line (@last_word) + end + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Next Word + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def next_word (i) + if @line_width + @word_length > @max_width + if @line_width == 0 + @last_word = i + @line_width = @word_length + @l_draw_count = @w_draw_count + @word_length = 0 + @w_draw_count = 0 + end + next_line (@last_word) + end + @last_word = i + @line_width += @word_length + @l_draw_count += @w_draw_count + @word_length = 0 + @w_draw_count = 0 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Proceed to Next Line + # last_word : the index of the beginning of the previous word + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def next_line (last_word) + @justify = false if last_word == @string.size + @string[last_word, 1] = "\x00" + if @last_space_counted + @line_width -= @bitmap.text_size (" ").width + @l_draw_count -= 1 + end + # Calculates the blank space left to cover in the line + line_blank = @max_width - @line_width + @line_space = ( line_blank.to_f / [(@l_draw_count.to_f - 1.0), 1.0].max ) + @break_loop = true + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Extract Curly Argument Code + # code : the code to extract + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def extract_curly_args_code (code, index) + @curly_argument += 1 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Extract Argument Code + # code : the code to extract + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def extract_args_code (code, index) + case code + when "\x0d" # Icon + @w_draw_count += 1 + @word_length += 24 + when "\x0e" # Bold + @bitmap.font.bold = (@string[index + 2, 1].to_i == 0) + when "\x0f" # Italic + @bitmap.font.italic = (@string[index + 2, 1].to_i == 0) + when "\x10" # Shadow + @bitmap.font.shadow = (@string[index + 2, 1].to_i == 0) + when "\x13" # Font Name + bmp = @bitmap + bmp.font.name = bmp.font.name.to_ary if !bmp.font.name.is_a? (Array) + @string[index, @string.size - index][/<(.*?)>/] + bmp.font.name = ([$1.to_s] + bmp.font.name).uniq + when "\x14" # Font Size + @string[index, @string.size - index][/<(\d*)>/] + @bitmap.font.size = $1.to_i + when "\xb1" # Set Contents_X + @string[index, @string.size - index][/<(\d*)>/] + @word_length = $1.to_i + end + @code_argument += 1 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Extract No Argument Code + # code : the code to extract + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def extract_no_args_code (code, index) + if ["\x00", "\x1d"].include? (code) # New line or New Page + if @line_width + @word_length > @max_width + next_line (@last_word) + else + @line_width += @word_length + @l_draw_count += @w_draw_count + line_blank = @max_width - @line_width + @line_space = ( line_blank.to_f / [(@l_draw_count.to_f - 1.0), 1.0].max ) + @break_loop = true + @justify = false + @force_break = true + end + elsif code == "\x09" # Tab + next_word (index) + @line_width = $game_message.justified_text ? @line_width + 32 :((@line_width / 32) + 1)*32 + end + end +end + +#============================================================================== +# ** Sprite MessageFace +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This sprite displays face graphics +#============================================================================== + +class Sprite_MessageFace < Sprite_Base + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (face_file, face_index, viewport = nil) + super (viewport) + wdth, hght = $game_message.face_width, $game_message.face_height + face = Cache.face (face_file) + if face_file[/^\$/] != nil # SINGLE + wdth = face.width if wdth <= 0 + wdth = [face.width, wdth].min + hght = face.height if hght <= 0 + hght = [face.height, hght].min + rect = Rect.new((face.width-wdth) / 2, (face.height-hght) / 2, wdth, hght) + else + # Resize if face smaller than allotted size + wdth = face.width / 4 if wdth <= 0 + wdth = [face.width / 4, wdth].min + hght = face.height / 2 if hght <= 0 + hght = [face.height / 2, hght].min + rect = Rect.new(0, 0, wdth, hght) + rect.x = (face_index % 4) * wdth + ((face.width / 4) - wdth) / 2 + rect.y = (face_index / 4) * hght + ((face.height / 2) - hght) / 2 + end + self.bitmap = Bitmap.new (rect.width, rect.height) + self.bitmap.blt(0, 0, face, rect) + self.mirror = $game_message.face_mirror + self.blend_type = $game_message.face_blend_type + self.visible = false + self.opacity = $game_message.face_fadein ? 0 : $game_message.face_opacity + $game_message.face_width, $game_message.face_height = rect.width, rect.height + end +end + +#============================================================================== +# ** Sprite SpeechTag +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This sprite displays the tag when speaking +#============================================================================== + +class Sprite_SpeechTag < Sprite + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variable + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + attr_reader :speech_tag_index + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (viewport = nil, *args) + @border_sprite = Sprite.new (viewport) + super (viewport, *args) + @border_sprite.z = self.z + 1 + @bitmaps = [] + reset_graphic + self.visible = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def dispose (*args) + if self.bitmap + self.bitmap.dispose + @border_sprite.bitmap.dispose + end + @border_sprite.dispose + super (*args) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Reset Graphic + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def reset_graphic (index = $game_message.speech_tag_index) + return if @speech_tag_index == index + @speech_tag_index = index + return if index < 0 + begin + bitmap = Cache.system ($game_message.speech_tag_graphics[index]) + rescue + return + end + @bitmaps.each { |bmp| bmp.dispose unless bmp.disposed? } + @bitmaps.clear + dummy_bmp = Bitmap.new (bitmap.width / 2, bitmap.height / 2) + src_rect = Rect.new (0, 0, bitmap.width / 2, bitmap.height / 2) + for i in 0...4 + new_bmp = dummy_bmp.dup + src_rect.x, src_rect.y = (i % 2)*new_bmp.width, (i / 2)*new_bmp.height + new_bmp.blt (0, 0, bitmap, src_rect) + @bitmaps.push (new_bmp) + end + dummy_bmp.dispose + self.bitmap = @bitmaps[0] + @border_sprite.bitmap = @bitmaps[1] + self.back_opacity = $game_message.message_backopacity + self.opacity = $game_message.message_opacity + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Direction + # dir : 0 => Down; 2 => Up + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_direction (dir) + self.bitmap = @bitmaps[dir] + @border_sprite.bitmap = @bitmaps[dir + 1] + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Back Opacity + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias back_opacity= opacity= unless self.method_defined? (:back_opacity=) unless $@ + def opacity= (value) + self.back_opacity = (self.opacity*(value.to_f / 255.0)).to_i + @border_sprite.opacity = value + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Visible + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def visible= (boolean) + super (boolean) + @border_sprite.visible = boolean + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * X=, Y=, Z=, Angle + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for attr in ["x", "y", "z", "angle"] + ATS_ATTR = <<_END_ + def #{attr}= (*args) + old_val = self.#{attr} + super (*args) + diff = self.#{attr} - old_val + @border_sprite.#{attr} += diff + end +_END_ + eval (ATS_ATTR) + end +end + +#============================================================================== +# *** Window_MessageBase +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This module holds methods shared by all ATS windows +#============================================================================== + +module Window_MessageBase + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Message Draw Icon + # allows for different opacities + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def message_draw_icon (icon_index, x, y, o = $game_message.message_fontalpha) + bitmap = Cache.system("Iconset") + rect = Rect.new(icon_index % 16 * 24, icon_index / 16 * 24, 24, 24) + self.contents.blt(x, y, bitmap, rect, o) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Text Color + # n : either a windowskin palette or the array + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def text_color (n) + if n.is_a? (Array) + return Color.new (*n) + else + return super (n) + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Alignment + # align : The alignment ( 1 => Centre, 2 => Right ) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_alignment (align = 1) + # Get rest of line + formatter = P_Formatter_ATS.new (self.contents.font.dup) + ls, lc, j = formatter.format_by_line (@text.dup, @contents_width - @contents_x) + formatter.dispose + es = ls * (lc - 1) + @contents_x += (es / 2)*align + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Message Character + # c : the character to be drawn + #`````````````````````````````````````````````````````````````````````````` + # This method utilizes the codes shared between the MessageBox, WordBox, + # and ChoiceBox + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_message_character (c, bmp = self.contents) + @text = "" if @text.nil? + case c + when "\x01" # \C[n] (text character color change) + @text.sub! (/<(#?)(.+?)>/i, "") + if $1 == "" + bmp.font.color = text_color($2.to_i) + if $game_message.message_fontalpha != 255 + bmp.font.color.alpha = $game_message.message_fontalpha + end + else + a = $2.to_s.scan (/../) + bmp.font.color = text_color ([a[0].to_i (16), a[1].to_i (16), a[2].to_i (16), $game_message.message_fontalpha]) + end + when "\x09" # Tab + x = @line_x + (((@contents_x - @line_x).to_i / 32) + 1)*32 + highlight_underline (x - @contents_x) + @contents_x = x + when "\xb1" # Set Contents X + @text.sub! (/<(\d*)>/, "") + @contents_x = $1.to_i + return false if self.is_a? (Window_Message) + when "\x0d" # Icon + @text.sub!(/<(\d*)>/, "") + highlight_underline (24) + message_draw_icon ($1.to_i, @contents_x, @contents_y) + @contents_x += 24 + @contents_x += @ls if self.is_a? (Window_Message) + when "\x0e" # Bold + @text.sub!(/<([01])>/, "") + bmp.font.bold = (Game_ATS::ATS_2 && $1.to_i == 0) ? !bmp.font.bold : $1.to_i == 0 + when "\x0f" # Italics + @text.sub!(/<([01])>/, "") + bmp.font.italic = (Game_ATS::ATS_2 && $1.to_i == 0) ? !bmp.font.italic : $1.to_i == 0 + when "\x10" # Shadow + @text.sub!(/<([01])>/, "") + bmp.font.shadow = (Game_ATS::ATS_2 && $1.to_i == 0) ? !bmp.font.shadow : $1.to_i == 0 + when "\x11" # Underline + @text.sub!(/<([01])>/, "") + @underline = (Game_ATS::ATS_2 && $1.to_i == 0) ? !@underline : $1.to_i == 0 + when "\x12" # Highlight + @text.sub! (/<(-?\d+)>/, "") + @highlight = $1.to_i + when "\x13" # Font Name + bmp.font.name = bmp.font.name.to_ary if !bmp.font.name.is_a? (Array) + @text.sub!(/<(.*?)>/, "") + bmp.font.name = ([$1.to_s] + bmp.font.name).uniq + when "\x14" # Font Size + @text.sub!(/<(\d*)>/, "") + bmp.font.size = $1.to_i + when "\x15" # Font Alpha + @text.sub! (/<(\d*)>/, "") + $game_message.message_fontalpha = $1.to_i + bmp.font.color.alpha = $game_message.message_fontalpha + when "\x18" # Alignment + @text.sub! (/<([012])>/, "") + return true if @align == $1.to_i + @align = $1.to_i + set_alignment (@align) + else + return false + end + return true + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Regular Character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_regular_character (c, bmp = self.contents) + c_width = bmp.text_size(c).width + c_width += @ls if !@ls.nil? + highlight_underline (c_width) + bmp.draw_text(@contents_x, @contents_y, c_width + 4, @wlh, c) + @contents_x += c_width + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Highlight and Underline section + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def highlight_underline (c_width) + # Highlight + if @highlight >=0 + hl_rect = Rect.new (@contents_x, @contents_y + 1, c_width, @wlh - 2) + colour = text_color (@highlight) + colour.alpha = 128 if colour.alpha > 128 + contents.fill_rect (hl_rect, colour) + end + # Underline + if @underline + y = @contents_y + contents.font.size + (@wlh - contents.font.size) / 2 + contents.fill_rect (@contents_x, y, c_width, 2, contents.font.color) + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Background Sprite + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def create_back_sprite (dim, use_dim) + @back_sprite.viewport = self.viewport + bmp = Cache.system(dim) + @back_sprite.bitmap = Bitmap.new (self.width, self.height) + @back_sprite.bitmap.stretch_blt (@back_sprite.bitmap.rect, bmp, bmp.rect) + if use_dim.is_a? (Integer) + @back_sprite.visible = use_dim == 0 ? false : use_dim == 1 ? true : $game_message.background == 1 + else + @back_sprite.visible = use_dim + end + @back_sprite.x = self.x + @back_sprite.y = self.y + @back_sprite.z = self.z - 10 + self.opacity = 0 if @back_sprite.visible + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * X, Y, Z + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for attr in ["x", "y", "z"] + ATS_NAMATTR = <<__END__ + def #{attr}= (*args) + old_val = self.#{attr} + super (*args) + diff = self.#{attr} - old_val + @back_sprite.#{attr} += diff + end +__END__ + eval (ATS_NAMATTR) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Get Top Row + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def top_row + return super if !@wlh + return self.oy / @wlh + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Top Row + # row : row shown on top + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def top_row=(row) + if !@wlh + super (row) + else + row = 0 if row < 0 + self.oy = row * @wlh + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Get Number of Rows Displayable on 1 Page + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def page_row_max + return super if !@wlh + return (self.height - 32) / @wlh + end +end + +#============================================================================== +# ** Window_WordBox +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This window will show a single line of text +#============================================================================== + +class Window_WordBox < Window_Base + include Window_MessageBase + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (viewport, word, x = 0, y = 0, width = -1, height = 56, name = false) + @back_sprite = Sprite.new (viewport) + @text = $game_message.convert_special_characters (word) + @wlh = name ? $game_message.name_wlh : $game_message.word_wlh + @wlh = $game_message.wlh if @wlh < 0 + height = 32 + (((word.scan (/\x00/).size) + 1)*@wlh) if height == -1 + @underline = false + @highlight = -1 + # Get Text Size + dummy_formatter = P_Formatter_ATS.new + lines = (@text + "\x00").scan (/.+?\x00/) + tw = 32 + for line in lines + ls, lc, j = dummy_formatter.format_by_line (line, 5000) + tw1 = 5000 - (ls * (lc - 1)) + tw = tw1 if tw1 > tw + end + dummy_formatter.dispose + if width < 0 + width = [(name ? tw + ($game_message.name_border_size*2) + 4 : tw + 36), 33].max + end + super (x, y, width, height) + self.viewport = viewport + self.openness = 0 + self.z = 300 + set_stats + @contents_x, @line_x = 0, 0 + @contents_y = 0 + @contents_width = contents.width + @align = 0 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Stats + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_stats + self.windowskin = Cache.system ($game_message.word_windowskin) + self.opacity = $game_message.word_opacity + self.back_opacity = $game_message.word_backopacity + create_back_sprite ($game_message.word_dim, $game_message.word_use_dim) + self.contents.font.name = $game_message.word_fontname + self.contents.font.size = $game_message.word_fontsize + self.contents.font.color = text_color ($game_message.word_fontcolour) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def dispose (*args) + @back_sprite.bitmap.dispose + @back_sprite.dispose + super (*args) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Word + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_word (text = @text, bmp = self.contents) + @text = text.dup + loop do + c = @text.slice!(/./m) # Get next text character + # Stop when text finished + break if c.nil? + if !draw_message_character (c, bmp) + if c == "\x00" + @contents_y += @wlh + @contents_x, @line_x = 0, 0 + set_alignment (@align) + else + draw_regular_character (c, bmp) + end + end + end + end +end + +#============================================================================== +# ** Window_NameBox +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This window shows the name +#============================================================================== + +class Window_NameBox < Window_WordBox + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (name, viewport = nil) + @word_sprite = Sprite_Base.new (viewport) + wlh = $game_message.name_wlh > 0 ? $game_message.name_wlh : $game_message.wlh + rows = (name.scan (/\x00/).size) + 1 + hght = [($game_message.name_border_size*2) + rows*wlh, 33].max + super (viewport, name, 0, 0, -1, hght, true) + self.viewport = viewport + @word_sprite.x, @word_sprite.y = $game_message.name_border_size, $game_message.name_border_size + @word_sprite.bitmap = Bitmap.new (self.width - (2*$game_message.name_border_size), self.height - (2*$game_message.name_border_size)) + @word_sprite.bitmap.font.name = $game_message.name_fontname + @word_sprite.bitmap.font.size = $game_message.name_fontsize + @word_sprite.bitmap.font.color = text_color ($game_message.name_fontcolour) + @contents_x, @line_x = 2, 2 + draw_word (name, @word_sprite.bitmap) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Stats + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_stats + self.opacity = $game_message.name_opacity + self.back_opacity = $game_message.name_backopacity + create_back_sprite ($game_message.name_dim, $game_message.name_use_dim) + self.windowskin = Cache.system ($game_message.name_windowskin) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def dispose (*args) + super (*args) + @word_sprite.bitmap.dispose + @word_sprite.dispose + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * X, Y, Z + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for attr in ["x", "y", "z"] + ATS_NAMATTR = <<__END__ + def #{attr}= (*args) + old_val = self.#{attr} + super (*args) + diff = self.#{attr} - old_val + @word_sprite.#{attr} += diff + end +__END__ + eval (ATS_NAMATTR) + end +end + +#============================================================================== +# ** Window_FaceBox +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This window processes faces +#============================================================================== + +class Window_FaceBox < Window_Base + include Window_MessageBase + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + attr_reader :busy + attr_reader :face_sprites + attr_accessor :scroll_x + attr_accessor :scroll_y + attr_accessor :scroll_x_speed + attr_accessor :scroll_y_speed + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (viewport) + @back_sprite = Sprite.new (viewport) + @face_sprites = [] + @animate_face_count = 0 + @fade_count = 0 + @active_face = 0 + @busy = false + @scroll_x = 0 + @scroll_y = 0 + @scroll_x_speed = 8 + @scroll_y_speed = 8 + super (0, 0, 128, 128) + self.viewport = viewport + self.visible = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Remake Window + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def remake_window (width = $game_message.face_width, height = $game_message.face_height) + self.width = [width + 2*$game_message.face_border_size, 33].max + self.height = [height + 2*$game_message.face_border_size, 33].max + create_contents + self.windowskin = Cache.system ($game_message.face_windowskin) + if $game_message.face_fadein + self.back_opacity = 0 + @fade_count = $game_message.face_opacity + else + self.back_opacity = $game_message.face_opacity + end + create_back_sprite ($game_message.face_dim, $game_message.face_use_dim) + self.visible = $game_message.face_window + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def dispose (*args) + super (*args) + clear + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Clear + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def clear + @face_sprites.each { |face| face.dispose } # Dispose all faces + @face_sprites.clear + return if self.disposed? + self.visible = false + @busy = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def update + if !@face_sprites.empty? && @face_sprites[@active_face].visible + # Update Fade In + if @fade_count > 0 + speed = [$game_message.face_fade_speed, @fade_count].min + @fade_count -= speed + self.back_opacity += speed + @face_sprites.each { |sprite| sprite.opacity += speed } + end + # Update Horizontal Scroll + if @scroll_x != 0 + speed = @scroll_x > 0 ? [@scroll_x_speed, @scroll_x].min : [-1*@scroll_x_speed, @scroll_x].max + self.x += speed + @scroll_x -= speed + end + # Update Verticaltal Scroll + if @scroll_y != 0 + speed = @scroll_y > 0 ? [@scroll_y_speed, @scroll_y].min : [-1*@scroll_y_speed, @scroll_y].max + self.y += speed + @scroll_y -= speed + end + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Animate Face + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def animate_face + if @face_sprites.size > 1 + @face_sprites[@active_face].visible = false + @animate_face_count = (@animate_face_count + 1) % $game_message.letters_per_face + @active_face = (@active_face + 1) % @face_sprites.size if @animate_face_count == 0 + @face_sprites[@active_face].visible = true + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Pause Animation + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def pause + return if @face_sprites.size < 2 + @face_sprites[@active_face].visible = false + @active_face = 0 + @face_sprites[0].visible = true + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Face + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_face (face_file, index = 0) + clear + name = face_file.dup + loop do + # Make face and store in @face_sprites unless invalied + face = Sprite_MessageFace.new (name, index, self.viewport) rescue break + @face_sprites.push (face) + break unless $game_message.animate_faces + # If face_file has a number appended, add all of it's animations + if name[/\_(\d+)$/i] != nil + name.sub! (/(\d+)$/) { ($1.to_i + 1).to_s } + elsif name[/^\!\[(\d+)\]./] != nil # If name has exclamation code + # Take all animations from the same face file + index += 1 + break if index == $1.to_i + else + break + end + end + @active_face = 0 + @face_sprites[@active_face].visible = true if @face_sprites[@active_face] + @busy = true + remake_window + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Goal X; Goal Y : accomodate for scrolling + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def goal_x + return self.x + @scroll_x + end + def goal_y + return self.y + @scroll_y + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * X=; Y=; Z= + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def x= (value) + super (value) + @face_sprites.each { |sprite| sprite.x = self.x + $game_message.face_border_size} + end + def y= (value) + super (value) + @face_sprites.each { |sprite| sprite.y = self.y + $game_message.face_border_size} + end + def z= (value) + super (value) + @face_sprites.each { |sprite| sprite.z = self.z } + end +end + +#============================================================================== +# ** Window_ChoiceBox +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This makes a separate window for handling choices, if desired. +#============================================================================== + +class Window_ChoiceBox < Window_Selectable + include Window_MessageBase + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (viewport = nil) + font = Font.new ($game_message.choice_fontname, $game_message.choice_fontsize) + @wlh = $game_message.choice_wlh > 0 ? $game_message.choice_wlh : $game_message.wlh + width, height, line_num = prepare_choices (font.dup) + @back_sprite = Sprite.new (viewport) + super (0, 0, width, height) + if line_num*@wlh > (height - 32) + self.contents.dispose + self.contents = Bitmap.new (width - 32, line_num*@wlh) + end + self.viewport = viewport + self.windowskin = Cache.system ($game_message.choice_windowskin) + self.opacity = $game_message.choice_opacity + self.back_opacity = $game_message.choice_backopacity + self.openness = 0 + self.contents.font = font + self.contents.font.color = text_color ($game_message.choice_fontcolour) + create_back_sprite ($game_message.choice_dim, $game_message.choice_use_dim) + @spacing = $game_message.choice_spacing + @column_max = $game_message.column_max + @item_max = $game_message.choices.size + @underline = false + @highlight = -1 + @last_index = -1 + refresh + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Prepare Choices + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def prepare_choices (font) + max_width = ($game_message.choice_width > 32 ? $game_message.choice_width : Graphics.width) - 32 + max_width = (max_width - ($game_message.choice_spacing*($game_message.column_max - 1))) / $game_message.column_max + longest_line = 1 + line_num = 0 + formatter = P_Formatter_ATS.new (font) + @group_sizes = [] + @choices = [] + group_size = 1 + $game_message.choices.each_index { |i| + lines = [] + line_sizes = [] + if i % $game_message.column_max == 0 + if i != 0 + line_num += group_size + @group_sizes.push (group_size) + end + group_size = 1 + end + string = $game_message.choices[i].dup + while !string.empty? + ls, lc, j = formatter.format_by_line (string, max_width) + length = max_width - (ls*(lc - 1)) + string.sub! (/(.*?)\x00/) { "" } + lines.push ($1.to_s) + line_sizes.push (length) + longest_line = [length, longest_line].max + end + group_size = [group_size, lines.size].max + @choices.push ([lines, line_sizes]) + } + line_num += group_size + @group_sizes.push (group_size) + if $game_message.choice_width > 32 + width = $game_message.choice_width + @line_size = max_width + else + width = 36 + ((longest_line + $game_message.choice_spacing)*$game_message.column_max) - $game_message.choice_spacing + @line_size = longest_line + end + height = $game_message.choice_height > 32 ? $game_message.choice_height : 32 + (@wlh*[line_num, $game_message.row_max].min) + return width, height, line_num + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Refresh + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def refresh + @choices.each_index { |i| + @align = 0 + rect = item_rect (i) + start_x = rect.x + @contents_y = rect.y + for j in 0...@choices[i][0].size + @text = @choices[i][0][j].dup + length = @choices[i][1][j] + @contents_x, @line_x = start_x, start_x + @contents_width = start_x + @line_size + set_alignment (@align) + while !@text.empty? + c = @text.slice! (/./m) + draw_regular_character (c) if !draw_message_character (c) + end + @contents_y += @wlh + end + } + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Item Rect + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def item_rect(index) + rect = super (index) + rect.y = 0 + for j in 0...(index / $game_message.column_max) + rect.y += @group_sizes[j] + end + rect.y *= @wlh + rect.height = @group_sizes[index / $game_message.column_max]*@wlh + return rect + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update Cursor + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def update_cursor + if @index < 0 # If the cursor position is less than 0 + self.cursor_rect.empty # Empty cursor + else # If the cursor position is 0 or more + @help_window.set_text (@index) if @help_window + return if @index == @last_index + if $game_message.skip_choices.include? (@index) + mod = @index - @last_index + @index = (@index + mod) % $game_message.choice_max + return + end + rect = item_rect(@index) # Get rectangle of selected item + row = rect.y / @wlh # Get current row + # Scroll up if before the currently displayed + self.top_row = row if row < top_row + # Scroll down if after the currently displayed + row += (rect.height / @wlh) - 1 + self.bottom_row = row if row > bottom_row + rect.y -= self.oy # Match rectangle to scroll position + self.cursor_rect = rect # Refresh cursor rectangle + @last_index = @index + end + end +end + +#============================================================================== +# ** Window ChoiceHelp +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# This window shows help text when hovering over choices +#============================================================================== + +class Window_ChoiceHelp < Window_Base + include Window_MessageBase + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def initialize (choice_helps) + @back_sprite = Sprite.new + @choice_helps = [] + @underline = false + @highlight = -1 + font = Font.new ($game_message.choicehelp_fontname, $game_message.choicehelp_fontsize) + p_formatter = P_Formatter_ATS.new (font) + longest_line = 1 + x, y = $game_message.choicehelp_x, $game_message.choicehelp_y + width, height = $game_message.choicehelp_width, $game_message.choicehelp_height + @wlh = $game_message.choicehelp_wlh < 0 ? $game_message.wlh : $game_message.choicehelp_wlh + wdth = width > 0 ? width - 32 : 5000 + tallest = 1 + choice_helps.each { |string| + s_d = $game_message.convert_special_characters (string.dup) + s_d.gsub! (/\x16/) { "\x00" } + lines = [] + line_lengths = [] + while !s_d.empty? + ls, lc, j = p_formatter.format_by_line (s_d, wdth) + line_lengths.push (wdth - (ls * (lc - 1))) + lines.push (s_d.slice! (/.*?\x00/)) + end + tallest = [tallest, lines.size].max + @choice_helps.push ([lines, line_lengths]) + } + p_formatter.dispose + width = line_lengths.max + 32 if width < 0 + x = x >= 0 ? x : [(Graphics.width - width) / 2, 0].max + y = $game_message.choicehelp_y < 0 ? 0 : $game_message.choicehelp_y + width = [33, [width, Graphics.width - x].min].max + height = height < 0 ? 32 + (tallest*@wlh) : [height, 33].max + super (x, y, width, height) + @contents_width = contents.width + self.windowskin = Cache.system ($game_message.choicehelp_windowskin) + self.opacity = $game_message.choicehelp_opacity + self.back_opacity = $game_message.choicehelp_backopacity + self.openness = 0 + create_back_sprite ($game_message.choicehelp_dim, $game_message.choicehelp_use_dim) + self.contents.font = Font.new ($game_message.choicehelp_fontname, $game_message.choicehelp_fontsize) + self.contents.font.color = text_color ($game_message.choicehelp_fontcolour) + @index = -1 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Text + # index : the index of the choice being highlighted + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_text (index) + if index != @index + return if self.disposed? + contents.clear + @index = index + lines = @choice_helps[index][0] + lengths = @choice_helps[index][1] + @contents_y = $game_message.choicehelp_center ? (contents.height - (lines.size*@wlh)) / 2 : 0 + @align = 0 + lines.each_index { |i| + @text = lines[i].dup + @contents_x, @line_x = 0, 0 + set_alignment (@align) + while !@text.empty? # Stop when text finished + c = @text.slice!(/./m) # Get next text character + draw_regular_character (c) if !draw_message_character (c) + end + @contents_y += @wlh + } + end + end +end + +#============================================================================== +# ** Window_Gold +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# Make @closing and @opening publicly accessible +#============================================================================== + +class Window_Gold + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + attr_reader :closing + attr_reader :opening +end + +#============================================================================== +# ** Window_Message +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# overwritten methods - create_back_sprite; update_message; update_cursor; +# convert_special_characters; reset_window; draw_face; input_pause; z=; +# close; visible= +# aliased methods - initialize; dispose; update; update_show_fast; new_page; +# start_message; new_line; finish_message; start_choice; terminate_message +# start_number_input; input_choice +# new methods - create_namebox; create_wordbox; create_choicebox; +# create_choicehelp; remake_window; fit_window_to_text; set_position; +# position_to_character; do_not_obscure_characters; set_face_position; +# set_speechtag_position; set_name_position; set_choice_position; +# dispose_ats_windows; update_letter_se; format_line; start_scroll_message; +# contents_width; draw_message_character; set_alignment +#============================================================================== + +class Window_Message + include Window_MessageBase + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modalg_ats3_win_msg_init_9id1 initialize unless $@ + def initialize (viewport = nil, *args) + @back_sprite = Sprite.new + modalg_ats3_win_msg_init_9id1 (*args) + remake_window + self.viewport = viewport ? viewport : Viewport.new (0, 0, Graphics.width, Graphics.height) + self.viewport.z = self.z + @face_window = Window_FaceBox.new (self.viewport) + @speechtag_sprite = Sprite_SpeechTag.new (self.viewport) + @longest_line = 0 + @scrolling = 0 + @review_scroll = 0 + @letter_se_count = 0 + @ls = 0 + @underline = false + @highlight = -1 + @last_index = -1 + @align = 0 + @max_oy = 0 + @p_formatter = P_Formatter_ATS.new (self.contents.font.dup) + @wlh = $game_message.wlh + @anti_update = true if self.is_a? (Window_BattleMessage) # Yanfly Melody compatibility measure + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modrna_atxts3_dsps_9th5 dispose unless $@ + def dispose (*args) + modrna_atxts3_dsps_9th5 (*args) # Run Original Method + dispose_ats_windows + @face_window.dispose + @speechtag_sprite.dispose + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mdrnalg_advtxt3_upd_3xa2 update unless $@ + def update (*args) + # Graphic Novel Support + if $game_message.graphic_novel && !self.is_a? (Window_BattleMessage) + if $game_message.gn_press_or_toggle # Press + self.viewport.visible = !Input.press? ($game_message.hide_button) + else # Toggle + self.viewport.visible = !self.viewport.visible if Input.trigger? ($game_message.hide_button) + end + return if !self.viewport.visible + end + [@choice_window, @choicehelp_window, @word_window, @name_window].each { |window| + window.update if window && !window.disposed? } + mdrnalg_advtxt3_upd_3xa2 (*args) + # If Window opening, but no text and just a choice window. + if @opening && $game_message.choice_window && @text.empty? && !$game_message.choices.empty? + close + $game_message.visible = @closing + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Background Sprite + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def create_back_sprite + if @back_sprite + @back_sprite.bitmap.dispose if @back_sprite.bitmap && !@back_sprite.bitmap.disposed? + @back_sprite.dispose if !@back_sprite.disposed? + end + @back_sprite = Sprite.new (self.viewport) + bmp = Cache.system($game_message.message_dim) + @back_sprite.bitmap = Bitmap.new (self.width, self.height + 32) + @back_sprite.bitmap.stretch_blt (@back_sprite.bitmap.rect, bmp, bmp.rect) + @back_sprite.visible = (@background == 1) + @back_sprite.x = self.x + @back_sprite.y = self.y + @back_sprite.z = 190 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Namebox + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def create_namebox (name) + @name_window.dispose unless @name_window.nil? || @name_window.disposed? + return if name.empty? + @name_window = Window_NameBox.new (name, self.viewport) + @name_window.open + set_name_position + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Wordbox + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def create_wordbox (word) + @word_window.dispose unless @word_window.nil? || @word_window.disposed? + return if word.empty? + x = $game_message.word_x + y = $game_message.word_y == -1 ? @gold_window.y : $game_message.word_y + @word_window = Window_WordBox.new (self.viewport, word, x, y, $game_message.word_width, $game_message.word_height) + if $game_message.word_x == -1 + if $game_message.word_y == -1 && (@gold_window.opening || (@gold_window.openness == 255 && !@gold_window.closing)) + @word_window.x = @gold_window.x - @word_window.width + if @word_window.x < 0 + val = @gold_window.y == 0 ? @gold_window.height : -1*@word_window.height + @word_window.y += val + @word_window.x = (Graphics.width - @word_window.width) / 2 + end + else + @word_window.x = (Graphics.width - @word_window.width) / 2 + end + end + @word_window.draw_word + @word_window.open + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Choicebox + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def create_choicebox + @choice_window = Window_ChoiceBox.new (self.viewport) + @choice_window.z = self.z + 10 + @choice_window.open + set_choice_position + $game_message.choices.clear + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create ChoiceHelp Window + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def create_choicehelp + @choicehelp_window = Window_ChoiceHelp.new ($game_message.help_choices) + @choicehelp_window.open + @choicehelp_window.viewport = self.viewport + if $game_message.choicehelp_y < 0 + unless Graphics.height - @choicehelp_window.height < self.y + self.height + @choicehelp_window.y = Graphics.height - @choicehelp_window.height + end + end + @choicehelp_window.z = self.z + 5 + @choice_window.help_window = @choicehelp_window if @choice_window + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Remake Window + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def remake_window (width = $game_message.message_width, height = $game_message.message_height) + return if width <= 32 || height <= 32 + self.width, self.height = width, height + self.contents.dispose + self.contents = Bitmap.new (width - 32, height - 32) + create_back_sprite + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Fit Window to Text + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def fit_window_to_text + all_lines = $game_message.texts.dup + all_lines += $game_message.choices.dup if !$game_message.choice_window + return if all_lines.size < 1 + @longest_line = 0 + con_hght = [$game_message.max_lines, all_lines.size].min + $game_message.message_height = 32 + (con_hght*@wlh) + $game_message.max_lines = con_hght + # Get Width of longest line + dummy_formatter = P_Formatter_ATS.new + all_lines.each { |line| + c_line = $game_message.convert_special_characters (line.dup) + ls, lc, j = dummy_formatter.format_by_line (c_line, 5000) + @longest_line = [@longest_line, (5000 - ls * (lc - 1))].max + if @longest_line > (Graphics.width - 32) + @longest_line = Graphics.width - 32 + break + end + } + dummy_formatter.dispose + $game_message.message_width = 32 + @longest_line + $game_message.choice_on_line = false if $game_message.message_width + $game_message.choice_width > Graphics.width + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Position + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_position (x = $game_message.message_x, y = $game_message.message_y) + if $game_message.character >= 0 + position_to_character ($game_message.character, $game_message.char_ref) + self.x = $game_message.message_x + self.y = $game_message.message_y + else + # Set the y position, either directly or by default + if y == -1 + do_not_obscure_characters if $game_message.do_not_obscure + self.y = ((Graphics.height - $game_message.message_height) / 2)*@position + else + self.y = y + end + if self.y < @gold_window.height + @gold_window.y = Graphics.height - @gold_window.height + else + @gold_window.y = 0 + end + # Set the x position + if x == -1 # Centre by default + self.x = (Graphics.width - $game_message.message_width) / 2 + else + self.x = x + end + end + set_face_position + set_name_position + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Position To Character + # character_id : the character around which the message window is to go + # type : Over, Below, Left or Right of character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def position_to_character (character_id, type) + whatever_fits = (type == 4) + type = 0 if whatever_fits # Above if fits, else Under + character = character_id == 0 ? $game_player : $game_map.events[character_id] + # Do not change position if character does not exist + return if character == nil + # Do not change position if the character is not on screen. + return unless character.screen_x.between? (0, Graphics.width) && character.screen_y.between? (0, Graphics.height) + # Get the size of the character + bmp = Cache.character (character.character_name).clone + if character.character_name[/^.?\$/] == nil + c_wdth, c_hght = bmp.width / 12, bmp.height / 8 + else + c_wdth, c_hght = bmp.width / 3, bmp.height / 4 + end + bmp.dispose + # Centre X or Y depending on position + if type % 2 == 0 + # Centre X + x = [character.screen_x - (self.width / 2) , 0].max + x = (x + self.width <= Graphics.width) ? x : Graphics.width - self.width + else + # Centre Y + y = [character.screen_y - ((self.height + c_hght) / 2), 0].max + y = (y + self.height <= Graphics.height) ? y : Graphics.height - self.height + end + case type + when 0 # Over + if whatever_fits && character.screen_y - c_hght - self.height < 0 + position_to_character (character_id, 2) + return + end + y = [character.screen_y - c_hght - self.height, 0].max + when 1 # Left + x = [character.screen_x - (c_wdth / 2) - self.width, 0].max + when 2 # Below + y = [character.screen_y, Graphics.height - self.height].min + when 3 # Right + x = [character.screen_x + (c_wdth / 2), Graphics.width - self.width].min + end + $game_message.message_x = x + $game_message.message_y = y + if character_id >= 0 && $game_message.speech_tag_index >= 0 + set_speech_sprite_position (character.screen_x, character.screen_y, c_wdth, c_hght, type) + position_to_character (character_id, 2) if whatever_fits && !@speechtag_sprite.visible + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Do Not Obstruct Characters + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def do_not_obscure_characters + obscured = [0, 0, 0] + positions = [2, 0, 1] + positions.delete (@position) + positions.unshift (@position) + positions.each { |pos| + y = ((Graphics.height - $game_message.message_height) / 2)*pos + range = y..(y + self.height) + $game_message.obscure_characters.each { |id| + char = id == 0 ? $game_player : $game_map.events[id] + next if char.nil? + range2 = (char.screen_y - $game_message.obscure_buffer)..char.screen_y + obscured[pos] += 1 if range === range2.first || range2 === range.first + } + if obscured[pos] == 0 + @position = pos + return + end + } + @position = obscured.index (obscured.min) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Speech Sprite Position + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_speech_sprite_position (c_x, c_y, c_w, c_h, char_ref) + @speechtag_sprite.opacity = $game_message.message_opacity + @speechtag_sprite.set_direction (char_ref) + @speechtag_sprite.x = c_x - (@speechtag_sprite.width / 2) + if char_ref == 0 + return if $game_message.message_y < @speechtag_sprite.height - 16 + @speechtag_sprite.y = c_y - c_h - @speechtag_sprite.height + $game_message.message_y -= (@speechtag_sprite.height - 16) + @speechtag_sprite.visible = true + elsif char_ref == 2 + return if $game_message.message_y + (@speechtag_sprite.height - 16) > Graphics.height + @speechtag_sprite.y = c_y + $game_message.message_y += @speechtag_sprite.height - 16 + @speechtag_sprite.visible = true + else + @speechtag_sprite.visible = false + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Face Position + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_face_position (x = $game_message.face_x, y = $game_message.face_y) + return if @face_window.nil? || @face_window.disposed? + # Determine Goal X + if x == -1 + if $game_message.face_side + x = (self.x + 16 - $game_message.face_border_size) + $game_message.face_offset_x + else + x = (self.x + self.width - @face_window.width - 16 + $game_message.face_border_size) - $game_message.face_offset_x + end + end + if y == -1 + if @face_window.height < self.height + y = self.y + ((self.height - @face_window.height) / 2) # Centre + else + y = (self.y + self.height) - @face_window.height + end + y = self.y if y < 0 # Align to top of window if out of bounds + y += $game_message.face_offset_y + end + # Set up Horizontal Scroll + if $game_message.face_scroll_x + left = x + @face_window.width + right = Graphics.width - x + if left < right + @face_window.x = -1*@face_window.width + @face_window.scroll_x = left + @face_window.scroll_x_speed = left / $game_message.face_scroll_speed + else + @face_window.x = Graphics.width + @face_window.scroll_x = -1*right + @face_window.scroll_x_speed = right / $game_message.face_scroll_speed + end + else + @face_window.x = x + end + # Set up Vertical Scroll + if $game_message.face_scroll_y + up = y + @face_window.height + down = Graphics.height - y + if up < down + @face_window.y = -1*@face_window.height + @face_window.scroll_y = up + @face_window.scroll_y_speed = up / $game_message.face_scroll_speed + else + @face_window.y = Graphics.height + @face_window.scroll_y = -1*down + @face_window.scroll_y_speed = down / $game_message.face_scroll_speed + end + else + @face_window.y = y + end + # If there is overlap and meant to fit to text_size + if $game_message.fit_window_to_text && @longest_line > 0 + con_x, con_w = contents_width (-1) + $game_message.message_width = @longest_line + contents.width + 32 - con_w + remake_window + if self.x + self.width > Graphics.width + diff = self.x - (Graphics.width - self.width) + self.x -= diff + @face_window.x -= diff + end + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Name Position + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_name_position (x = $game_message.name_x, y = $game_message.name_y) + return if @name_window.nil? || @name_window.disposed? + # Set NameBox coordinates + if y == -1 + y = self.y - @name_window.height + $game_message.name_offset_y + y = self.y + self.height - $game_message.name_offset_y if y < 0 + end + if x == -1 + if $game_message.face_side + x = self.x + $game_message.name_offset_x + else + x = self.x + self.width - @name_window.width - $game_message.name_offset_x + end + # If face is showing + if @face_window.busy + # If overlap with face + if ((@face_window.goal_y + $game_message.face_border_size + 1).between? (y, y + @name_window.height) || + y.between? (@face_window.goal_y + $game_message.face_border_size + 1, @face_window.goal_y + @face_window.height)) && + (x.between? (@face_window.goal_x + $game_message.face_border_size, @face_window.goal_x + @face_window.width) || + @face_window.goal_x.between? (x, x + @name_window.width)) + if $game_message.face_side + x = @face_window.goal_x + @face_window.width + else + x = @face_window.goal_x - @name_window.width + end + end + end + end + @name_window.x = x + @name_window.y = y + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Choice Position + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_choice_position (x = $game_message.choice_x, y = $game_message.choice_y) + return if @choice_window.nil? || @choice_window.disposed? + if y == -1 + if self.openness == 0 + y = (Graphics.height - @choice_window.height) / 2 + else + y = self.y - @choice_window.height + $game_message.choice_offset_y + y = self.y + self.height - $game_message.choice_offset_y if y < 0 + end + end + if x == -1 + choice_side = $game_message.choice_opposite_face ? !$game_message.face_side : $game_message.face_side + if self.openness == 0 + x = (Graphics.width - @choice_window.width) / 2 + elsif choice_side + x = self.x - $game_message.choice_offset_x + else + x = self.x + self.width - @choice_window.width + $game_message.choice_offset_x + end + end + @choice_window.x = x + @choice_window.y = y + @choice_window.y = self.y if $game_message.choice_on_line + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Dispose Windows + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def dispose_ats_windows + ats_windows = [@name_window, @word_window, @choice_window, @choicehelp_window] + ats_windows.each { |window| window.dispose unless window.nil? || window.disposed? } + @name_window = nil + @word_window = nil + @choice_window = nil + @choicehelp_window = nil + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update Show Fast + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mrnagb_atsys3_updshfst_6yv3 update_show_fast unless $@ + def update_show_fast (*args) + real_count = @wait_count + @wait_count = 2 if $game_message.skip_disabled + mrnagb_atsys3_updshfst_6yv3 (*args) # Run Original Method + @wait_count = real_count + @face_window.update + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def update_message + loop do + if @scrolling > 0 + speed = [$game_message.scroll_speed, @scrolling].min + @scrolling -= speed + self.oy += speed + if @scrolling <= 0 && self.contents.height < self.oy + self.height - 32 + bmp = self.contents + # Max # of lines before cutting top out approx. 200 + if self.contents.width * (self.oy + self.height - 32) < 2500000 + self.contents = Bitmap.new (self.contents.width, self.oy + self.height - 32) + self.contents.font = bmp.font.dup + src_rect = bmp.rect + else + src_rect = Rect.new (0, @wlh, bmp.rect.width, bmp.rect.height - @wlh) + end + self.contents.blt (0, 0, bmp, src_rect) + bmp.dispose if src_rect.y != 0 + end + break + end + c = @text.slice!(/./m) # Get next text character + # If no text left + if c.nil? + if @choice_sizes + @choice_sizes[-1].push (@line_count - @choice_sizes[-1][0]) + end + if $game_message.choices.empty? + $game_message.choice_start = @choice_sizes[0][0] if @choice_sizes + finish_message # Finish update + break + else + if @choice_sizes.nil? + $game_message.scroll_by_page = false + if $game_message.choice_window + create_choicebox + finish_message + return + end + if !$game_message.scrolling + if $game_message.max_lines < @line_count + $game_message.choices.size + self.pause = true + $game_message.choice_start = 0 + @text = "\x1d" + end + $game_message.scrolling = true + return + end + @choice_sizes = [] + end + @text = "#{$game_message.choices.shift}\x00" + # New line stuff + format_line + @choice_sizes.push ([@line_count]) + c = @text.slice!(/./m) # Get next text character + end + end + draw_message_character (c) + break unless @show_fast || @line_show_fast || $game_message.message_speed < 0 + break if self.pause + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update Letter SE + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def update_letter_se + return if !$game_message.letter_sound || @show_fast || @line_show_fast || + $game_message.message_speed < 0 + # Randomize Pitch + rp = $game_message.random_pitch + $game_message.letter_se.pitch = rp.first + rand(rp.last - rp.first) unless rp.first == rp.last + # Update Sound + $game_message.play_se ($game_message.letter_se) if @letter_se_count == 0 + @letter_se_count = (@letter_se_count + 1) % $game_message.letters_per_se + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update cursor + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def update_cursor + return if @choice_window + if @index >= 0 + @choicehelp_window.set_text (@index) if @choicehelp_window + return if @index == @last_index + if $game_message.skip_choices.include? (@index) + mod = @last_index < @index ? 1 : -1 + @index = (@index + mod) % $game_message.choice_max + return + end + row = @choice_sizes[@index][0] + hght = @choice_sizes[@index][1]*@wlh + y = (row) * @wlh + x, con_width = contents_width (self.contents.height > self.height - 32 ? -1 : y) + # Scroll up if before the currently displayed + self.top_row = row if row < top_row + row += @choice_sizes[@index][1] - 1 + # Scroll down if after the currently displayed + self.bottom_row = row if row > bottom_row + self.cursor_rect.set(x, y - self.oy, con_width, hght) + @last_index = @index + else + self.cursor_rect.empty + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Start Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malg_atxs3_msgstrt_8ik3 start_message unless $@ + def start_message (*args) + @starting = true + @wlh = $game_message.wlh + @align = 0 + @max_oy = 0 + @line_x = 0 + @underline = $game_message.underline + @highlight = $game_message.highlight + malg_atxs3_msgstrt_8ik3 (*args) + @text.gsub! (/([^\s])\s*\x00/) { "#{$1} " } if $game_message.paragraph_format + # Remove x00 from middle of wb and nb boxes + @text.gsub! (/[\x19\x1a]{.*?}/) { |match| match.gsub (/\x00/) { "" } } + @text.gsub! (/\x16/) { "\x00" } + format_line + self.windowskin = Cache.system ($game_message.message_windowskin).dup + self.windowskin.clear_rect (80, 16, 32, 32) unless $game_message.scroll_show_arrows + self.contents.font.name = $game_message.message_fontname + self.contents.font.size = $game_message.message_fontsize + @p_formatter.bitmap.font = self.contents.font.dup + $game_message.play_se ($game_message.start_se) if $game_message.start_sound + @starting = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * New Page + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malba_advts3_nwpge_7uj2 new_page unless $@ + def new_page (*args) + if !$game_message.do_not_start || !@starting + if @text.sub! (/\A\x17<(\d+)>/, "") != nil + actor = $game_actors[$1.to_i] + if !actor.nil? + $game_message.face_name = actor.face_name + $game_message.face_index = actor.face_index + end + end + @face_window.clear if !@face_window.nil? && !@face_window.disposed? + malba_advts3_nwpge_7uj2 (*args) # Run Original Method + self.contents.font.color = text_color ($game_message.message_fontcolour) + self.contents.font.color.alpha = $game_message.message_fontalpha if $game_message.message_fontalpha != 255 + self.oy = 0 + elsif (@line_count - (self.oy / @wlh)) >= $game_message.max_lines + # If scrolling + start_scroll_message if $game_message.scrolling + end + @contents_x, @contents_width = contents_width + @line_x = @contents_x + unless @starting + @text.sub! ("\x00", " ") if @text && $game_message.paragraph_format && !(@p_formatter && @p_formatter.force_break) + format_line + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * New Line + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modalg_ats3_nl_scroll_0ol2 new_line unless $@ + def new_line (*args) + modalg_ats3_nl_scroll_0ol2 (*args) + @contents_y = @line_count*@wlh + @contents_x, @contents_width = contents_width (@contents_y) + @line_x = @contents_x + format_line + # If Reached end of page + if (@line_count - (self.oy / @wlh)) >= $game_message.max_lines + # Do not pause if message is over + return if @text == "" && ($game_message.choices.empty? || $game_message.choice_window) + # If scrolling + if $game_message.scrolling + start_scroll_message + else + self.pause = true + end + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Format Line + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def format_line + if $game_message.paragraph_format + @ls, dc, justify = @p_formatter.format_by_line (@text, @contents_width) + else + @ls = 0 + end + set_alignment (@align) if @align != 0 + @ls = 0 if @ls > 0 && (!justify || @align != 0) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Convert Special Characters + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def convert_special_characters + $game_message.convert_special_characters (@text) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Window Background and Position + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def reset_window + @background = $game_message.background + @position = $game_message.position + $game_message.choice_on_line = false if $game_message.choice_width < 32 + fit_window_to_text if $game_message.fit_window_to_text + if $game_message.choice_window && $game_message.choice_on_line + $game_message.message_width = [$game_message.message_width, Graphics.width - $game_message.choice_width].min + total_width = $game_message.message_width + $game_message.choice_width + if $game_message.message_x == -1 + $game_message.message_x = (Graphics.width - total_width) / 2 + elsif $game_message.message_x + total_width > Graphics.width + $game_message.message_x = Graphics.width - total_width + end + choice_side = $game_message.choice_opposite_face ? !$game_message.face_side : $game_message.face_side + if choice_side + $game_message.choice_x = $game_message.message_x + $game_message.message_x += $game_message.choice_width + else + $game_message.choice_x = $game_message.message_x + $game_message.message_width + end + $game_message.choice_height = $game_message.message_height + end + remake_window unless $game_message.do_not_start + # Set opacity + self.opacity = @background == 0 ? $game_message.message_opacity : 0 + self.back_opacity = $game_message.message_backopacity + # Remake speech bubble if different + @speechtag_sprite.reset_graphic + @speechtag_sprite.z = self.z + 1 + if @text.include? ("\x1c") + @text = "" + self.openness = 0 unless $game_message.do_not_refresh + end + set_position + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Finish Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malbra_ats3_fnmsg_7uj2 finish_message unless $@ + def finish_message (*args) + @face_window.pause + $game_message.play_se ($game_message.finish_se) if $game_message.finish_sound + malbra_ats3_fnmsg_7uj2 (*args) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Start Choices + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modrn_ats3_strtchc_5sf2 start_choice unless $@ + def start_choice (*args) + modrn_ats3_strtchc_5sf2 (*args) + $game_message.move_when_visible = false + if @choice_window + self.index = -1 + @choice_window.index = 0 + end + create_choicehelp if !$game_message.help_choices.empty? + @max_oy = [(@line_count - $game_message.max_lines)*@wlh, 0].max + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Start Number Input + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malgr_advts3_strtnuminp_8qw2 start_number_input unless $@ + def start_number_input (*args) + malgr_advts3_strtnuminp_8qw2 (*args) # Run Original Method + if $game_message.scrolling + self.oy += @wlh if @line_count >= $game_message.max_lines + @number_input_window.y -= self.oy + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Start Scroll Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def start_scroll_message + scroll_plus = @wlh + scroll_plus *= $game_message.max_lines if $game_message.scroll_by_page + @scrolling += scroll_plus + @show_fast = false + if $game_message.scroll_autopause && !@text.nil? && !@text.empty? + self.pause = true + $game_message.play_se ($game_message.pause_se) if $game_message.pause_sound + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * End Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modal_atsv3_trmmsg_8yx4 terminate_message unless $@ + def terminate_message (*args) + if !$game_message.do_not_refresh + if !@face_window.nil? && !@face_window.disposed? + if self.is_a? (Window_BattleMessage) + @face_window.visible = false + @face_window.clear + create_contents if contents.height > height - 32 + self.oy = 0 + end + end + dispose_ats_windows + else + @choice_window.dispose if @choice_window && !@choice_window.disposed? + @choicehelp_window.dispose if @choicehelp_window && !@choicehelp_window.disposed? + @choice_window = nil + @choicehelp_window = nil + end + $game_message.play_se ($game_message.terminate_se) if $game_message.terminate_sound + modal_atsv3_trmmsg_8yx4 (*args) # Run Original Method + @last_index = -1 + @choice_sizes = nil + @speechtag_sprite.visible = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Contents Width + # y : the line to check it on + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def contents_width (y = 0) + # If can scroll, make sure no overlap over whole range. Get the ranges + if y < 0 || ($game_message.scrolling && $game_message.texts.size > $game_message.max_lines) + m_range = (self.y + 16)..(self.y + 16 + ($game_message.max_lines*@wlh)) + else + screen_y = self.y + 16 + y - self.oy + m_range = screen_y..screen_y + @wlh + end + return 0, contents.width if @face_window.z < self.z + f_range = @face_window.goal_y..(@face_window.goal_y + @face_window.height) + # If there is overlap with the face window + if @face_window.busy && (m_range === f_range.first || f_range === m_range.first) + left = @face_window.goal_x - (self.x + 20) + right = self.x + self.width - 20 - (@face_window.goal_x + @face_window.width) + if left >= right + return 0, left + else + return @face_window.goal_x + @face_window.width - (self.x + 12), right + end + end + return 0, contents.width # Start X, total width + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Face + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_face (face_file, face_index, *args) + @face_window.set_face (face_file, face_index) + set_face_position + @face_window.z = self.z + $game_message.face_z + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Message Character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_message_character (c) + return if super (c) + case c + when "\x00" # New line + new_line + when "\x02" # \G (gold display) + @gold_window.refresh + @gold_window.openness > 0 ? @gold_window.close : @gold_window.open + when "\x03" # \W[x] (Wait x frames) + @text.sub!(/<(\d+)>/, "") + @wait_count = $1.to_i + @face_window.pause + @show_fast, @line_show_fast = false, false + when "\x04" # \! (Wait for input) + self.pause = true + $game_message.play_se ($game_message.pause_se) if $game_message.pause_sound + @show_fast, @line_show_fast = false, false + when "\x05" # \/@ (Fast display ON) + @text.sub!(/<([01])>/, "") + @line_show_fast = (Game_ATS::ATS_2 && $1.to_i == 0) ? !@line_show_fast: $1.to_i == 0 + when "\x06" # \/@@ (Fast display OFF) + @text.sub!(/<([01])>/, "") + @show_fast = (Game_ATS::ATS_2 && $1.to_i == 0) ? !@show_fast : $1.to_i == 0 + when "\x07" # \^ (No wait for input) + @pause_skip = true + when "\x08" # Speed Change + @text.sub!(/<(=?-?\d+)>/, "") + s = $1.to_s + s.sub! (/(=)/, "") + if $1.nil? + $game_message.message_speed = [$game_message.message_speed + s.to_i, -1].max + else + $game_message.message_speed = [s.to_i, -1].max + end + when "\x0b" # Show Animation + # Extract Target and Graphic ID + @text.sub! (/<(\d*),(\d*),([01])>/, "") + return if self.is_a? (Window_BattleMessage) + char = $1.to_i < 1 ? $game_player : $game_map.events[$1.to_i] # Target + if char != nil + $3.to_i == 0 ? char.animation_id = $2.to_i : char.balloon_id = $2.to_i + end + when "\x0c" # Position to Character + @text.sub! (/<(\d+),([0-4])>/, "") + return if self.is_a? (Window_BattleMessage) + $game_message.character = $1.to_i + $game_message.char_ref = $2.to_i + set_position + new_page + when "\x17" # Use Actor Face + @text.sub! (/<(\d+)>/, "") + $game_message.face_name = $game_actors[$1.to_i].face_name + $game_message.face_index = $game_actors[$1.to_i].face_index + draw_face ($game_message.face_name, $game_message.face_index) + set_name_position + when "\x19" # Name Box + @text.sub! (/{(.*?)}/, "") + create_namebox ($1.to_s) + when "\x1a" # Word Box + @text.sub! (/{(.*?)}/, "") + create_wordbox ($1.to_s) + when "\x1b" # Play SE + @text.sub! (/<(.+?),([MS]E)>/, "") + $game_message.play_se ((RPG.const_get ($2.to_s)).new ($1.to_s)) + when "\x1d" # New Page + create_contents + new_page + when "\x1f" # Position setting + @text.sub! (/<([012]),(-?\d+),(-?\d+)>/, "" ) + return if self.is_a? (Window_BattleMessage) + case $1.to_i + when 0 then set_position ($2.to_i, $3.to_i) + when 1 then set_face_position ($2.to_i, $3.to_i) + when 2 then set_name_position ($2.to_i, $3.to_i) + end + new_page unless $1.to_i == 2 + when "\x7f" # Evaluate Code + @text.sub! (/\{(.+?)#\}/, "") + eval ($1.to_s) + when "\xb0" # Enable/Disable Skipping + @text.sub!(/<([01])>/, "") + $game_message.skip_disabled = (Game_ATS::ATS_2 && $1.to_i == 0) ? !$game_message.skip_disabled : $1.to_i == 0 + @show_fast, @line_show_fast = false, false + when "\xb1" # Set Contents X + start_x, throwaway = contents_width (@contents_y) + @contents_x += start_x + else # Normal text character + draw_regular_character (c) + @wait_count += $game_message.message_speed if !@show_fast && + !@line_show_fast && $game_message.message_speed > 0 + update_letter_se + @face_window.animate_face + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Set Alignment + # align : The alignment ( 1 => Centre, 2 => Right ) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def set_alignment (align = 1) + # Get rest of line + formatter = P_Formatter_ATS.new (self.contents.font.dup) + txt = @text[/.*?\x00/] + txt = "" if txt.nil? + ls, lc, j = formatter.format_by_line (txt, @line_x + @contents_width - @contents_x) + formatter.dispose + es = ls * (lc - 1) + @contents_x += (es / 2)*align + @ls = 0 if align > 0 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Input Pause + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def input_pause (*args) + # Allow to review message before inputting + if $game_message.scroll_review + if @review_scroll != 0 + scroll_plus = @review_scroll > 0 ? [@review_scroll, $game_message.scroll_speed].min : + [@review_scroll, -1*$game_message.scroll_speed].max + self.oy += scroll_plus + self.oy = [[self.oy, 0].max, @max_oy].min + @review_scroll -= scroll_plus + end + if Input.press? (Input::UP) + @review_scroll -= $game_message.scroll_speed + elsif Input.press? (Input::DOWN) + @review_scroll += $game_message.scroll_speed + end + end + # Original Method + if Input.trigger?(Input::B) or Input.trigger?(Input::C) + self.oy = @max_oy + self.pause = false + if @text != nil and not @text.empty? + new_page if !$game_message.scrolling && @line_count >= $game_message.max_lines + else + terminate_message + end + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Choice Input + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modab_ats3_inptchc_7yh2 input_choice unless $@ + def input_choice (*args) + if Input.trigger?(Input::C) + indx = @choice_window ? @choice_window.index : self.index + if $game_message.disabled_choices.include? (indx) + Sound.play_buzzer + else + Sound.play_decision + $game_message.choice_proc.call(indx) + terminate_message + end + return + end + modab_ats3_inptchc_7yh2 (*args) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Pause= + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias malabra_ats3_setpause_8uj2 pause= unless (self.method_defined? (:malabra_ats3_setpause_8uj2) || $@) + def pause= (boolean) + malabra_ats3_setpause_8uj2 (boolean) + if boolean + @max_oy = self.oy + @face_window.pause + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Z= + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def z= (*args) + diff = -1*self.z + super (*args) + diff += self.z + ats_gfx = [@face_window, @name_window, @word_window, @speechtag_sprite, + @back_sprite, @choice_window, @choicehelp_window] + ats_gfx.each { |gfx| gfx.z += diff unless gfx.nil? } + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Visible = + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def visible= (boolean) + super (boolean) + @face_window.clear if @face_window && !@face_window.disposed? && !boolean + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Close + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def close (*args) + super (*args) + if !@face_window.nil? && !@face_window.disposed? + @face_window.clear + @face_window.close + end + @name_window.close if @name_window && !@name_window.disposed? + end +end + +#============================================================================== +# ** Window_BattleMessage +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# aliased method - initialize +# overwritten method - draw_line +#============================================================================== + +class Window_BattleMessage + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modrna_ynfl_melatscomp_5yh2 initialize unless $@ + def initialize (*args) # Yanfly Melody compatibility measure + modrna_ynfl_melatscomp_5yh2 (*args) + @anti_update = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Line + # index : the line to draw on + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_line(index) + return if !@text.nil? + rect = Rect.new(0, 0, 0, 0) + rect.x += 4 + rect.y += index * @wlh + rect.width = contents.width - 8 + rect.height = @wlh + self.contents.clear_rect(rect) + self.contents.font.color = normal_color + @wlh = $game_message.battle_wlh + @text = @lines[index].dup + convert_special_characters + @contents_x = rect.x + @contents_y = rect.y + loop do + c = @text.slice!(/./m) # Get next text character + # Stop when text finished + break if c.nil? + draw_message_character (c) + end + @text = nil + end +end + +#============================================================================== +# ** Scene_Title +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# aliased method - create_game_objects +#============================================================================== + +class Scene_Title + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Game Objects + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modalg_ats3_gm_obj_create create_game_objects unless $@ + def create_game_objects (*args) + # Create the object which holds default values for message + $game_ats = $ats_default = Game_ATS.new + # Run original method + modalg_ats3_gm_obj_create (*args) + end +end + +#============================================================================== +# ** Scene_File +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# aliased methods - write_save_data; read_save_data +#============================================================================== + +class Scene_File + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Write Save Data + # file : the file being written to + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modalg_ats3_write_save write_save_data unless $@ + def write_save_data (file, *args) + # Run Original Method + modalg_ats3_write_save (file, *args) + Marshal.dump($game_ats, file) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Read Save Data + # file : the file being read from + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modalg_ats3_read_save_data read_save_data unless $@ + def read_save_data (file, *args) + # Run Original Method + modalg_ats3_read_save_data (file, *args) + begin + $game_ats = $ats_default = Marshal.load (file) + rescue # Initialize if old save + $game_ats = $ats_default = Game_ATS.new + $game_message.clear + end + end +end diff --git a/Kawariki-patches/ports/Advanced-Text-System.rb b/Kawariki-patches/ports/Advanced-Text-System.rb new file mode 100644 index 0000000..dae721e --- /dev/null +++ b/Kawariki-patches/ports/Advanced-Text-System.rb @@ -0,0 +1,604 @@ +#============================================================================== +# ATS: Special Message Codes [VXA] +# Version: 1.0.6 +# Author: modern algebra (rmrk.net) +# Date: 18 October 2012 +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Description: +# +# This script allows you to use a number of additional special codes in the +# message system, which will function similar to the default codes like +# \v[n], \c[n], etc. Please see the list of special message codes starting at +# line 54 for a full idea of what is added. Basically, it gives you greater +# control over the delivery of a message, allows you to make use of font +# effects like bolding and italicizing, and allows you to retrieve and +# display useful data like the names of weapons and armors. +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# ATS Series: +# +# This script is part of the Advanced Text System series of scripts. These +# scripts are based off the Advanced Text System for RMVX, but since RMVX Ace +# has a much more sensibly designed message system, it is no longer necessary +# that it be one large script. For that reason, and responding to feedback on +# the ATS, I have split the ATS into multiple different scripts so that you +# only need to pick up the components for the features that you want. It is +# therefore easier to customize and configure. +# +# To find more scripts in the ATS Series, please visit: +# http://rmrk.net/index.php/topic,44525.0.html +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Instructions: +# +# This script is not difficult to use or configure. There are only ten +# configuration options for this script. These are: +# +# :message_speed :font_italic +# :show_fast_speed :font_outline +# :font_name :font_shadow +# :font_size :font_underline +# :font_bold :font_highlight +# +# :message_speed allows you to set the default number of frames to wait +# between drawing each letter in the message window. :show_fast_speed allows +# you to control the amount it speeds up when the player presses enter. You +# can gain instructions on setting the default value for both at line 180. +# +# All of the :font_ settings simply let you set the initial values for the +# font when used. +# +# As with other ATS scripts, you can change the value of these options in +# game with the following codes in a script call: +# +# ats_next(:message_option, x) +# ats_all(:message_option, x) +# +# Where :message_option is the symbol you want (:message_speed or +# :show_fast_speed) and x is the value you want to change it to. ats_next +# will only change it for the very next message, while ats_all will change it +# for every message to follow. +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# List of Special Message Codes: +# +# The default message codes are retained, and this script adds a number of +# other desirable features. The following is a complete list of the message +# codes at your disposal. Simply insert them into a Display Message command, +# Choice Branches, or any other message related system. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Font Effects: +# +# \fn[fontname] - Change the font to fontname. If you put multiple fontnames +# separated by commas, then it will take the first one the user has installed +# \fs[n] - Change the font size to n. +# \{ - Increase the font size by 8. +# \} - Decrease the font size by 8. +# \c[n] - Set the colour of the text being drawn to the nth colour of the +# Windowskin palette. 0 is the normal color and 16 is the system color. +# \hc[RRGGBB] or \c[#RRGGBB] - Set the colour of the text being drawn to any +# colour, using hexadecimal values. You can set each colour (red, green, +# blue) to anything from 0-255 (00-FF). You must use hexadecimal values. If +# so desired, you can add an additional hex value from 00-FF to change the +# alpha. +# \b - Turn bold on. Text drawn after this code will be bolded. +# /b - Turn bold off. +# \i - Turn italic on. Text drawn after this code will be italicized. +# /i - Turn italic off. +# \o - Turn outline on. +# /o - Turn outline off. +# \s - Turn shadow on. Text drawn after this code will have a shadow. +# /s - Turn shadow off. +# \u - Turn underline on. Text drawn after this code will be underlined. +# /u - Turn underline off. +# \hl[n] - Turn highlight on. Text drawn after this code will be highlighted +# with colour n + 1 from the windowskin palette +# /hl OR \hl[0] - Turn highlight off. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Message Control: +# +# \s[n] - Sets the message speed to wait n frames between drawing every letter. +# 0 is instant. +# \s[+n] - Sets the message speed to wait an additional n frames between drawing +# every letter. +# \s[-n] - Sets the message speed to wait n less frames between drawing every +# letter. +# \. - Wait 15 frames before drawing the next character +# \| - Wait 60 frames before drawing the next character +# \w[n] - Wait n frames before drawing the next character +# \! - Pause text and wait for player to press Enter +# \^ - Skip Pause; allows you to close a message window without requiring player +# input. +# \> - Show line fast. +# \< - Stop showing line fast. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Replacement Codes: +# +# \p[n] - Draw the name of the actor in the xth position in the party. 1 is +# the party leader, 2 is the second member, etc. +# \n[n] - Draw the name of the actor with ID n +# \nn[n] - Draw the nickname of the actor with ID n. +# \pid[n] - Draw the ID of the actor in the nth position in the party. +# \ac[n] - Draw the class name of the actor with ID n. +# \al[n] - Draw the level of the actor with ID n. +# \ni[n] - Draw the name of the item with ID n. +# \nw[n] - Draw the name of the weapon with ID n. +# \na[n] - Draw the name of the armor with ID n. +# \ns[n] - Draw the name of the skill with ID n. +# \nt[n] - Draw the name of the state with ID n. +# \nc[n] - Draw the name of the class with ID n. +# \ne[n] - Draw the name of the event with ID n on the current map. +# \nm[n] - Draw the name of the enemy with ID n. +# \nv[n] - Draw the name of the variable with ID n. +# \nsw[n] - Draw the name of the switch with ID n. +# \nl[n] - Draw the name of the element with ID n. +# \nwt[n] - Draw the name of the weapon type with ID n. +# \nat[n] - Draw the name of the armor type with ID n. +# \nst[n] - Draw the name of the skill type with ID n. +# \np[n] - Draw the name of the actor in the nth position in the party. +# \map - Draw the name of the map the player is currently on. +# \map[n] - Draw the name of the map with ID n. +# \pg - Draws the amount of money the party has. +# \g - Draws the unit of currency. +# \vocab[method] - Will draw whatever Vocab.method returns, if it is a valid +# method call. Suitable values for method are: level, level_a, hp, hp_a, +# mp, mp_a, tp, tp_a, fight, escape, attack, guard, item, skill, equip, +# status, formation, save, game_end, weapon, armor, key_item, equip2, +# optimize, clear, new_game, continue, shutdown, to_title, cancel, +# currency_unit +# \vocab[param, n] - Will draw the label for parameter with ID n. 0 => Max HP; +# 1 => Max MP; 2 => Attack; 3 => Defence; 4 => Magic; 5 => Magic Defence; +# 6 => Agility; 7 => Luck +# \vocab[etype, n] - Will draw the label for equipment type with ID n. +# 0 => Weapon; 1 => Shield; 2 => Head; 3 => Body; 4 => Accessory +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Other Effects: +# +# \$ - Opens a window which shows the party's gold. +# \n - line break +# \i[n] - Draw icon with index n. +# \ii[n] - Draw icon of the item with ID n. +# \iw[n] - Draw icon of the weapon with ID n. +# \ia[n] - Draw icon of the armor with ID n. +# \is[n] - Draw icon of the skill with ID n. +# \it[n] - Draw icon of the state with ID n. +# \x[n] - Draw next character at pixel n of the window contents. +# \s{n,text} - Will only draw text if the switch with ID n is ON. +# \s!{n,text} - Will only draw text if the switch with ID n is OFF. +# \#{code} - This will evaluate code. So, if you know scipting, you can place +# any code there and it will draw whatever is returned by it. For instance: +# \#{$game_system.save_count} would draw the number of times the player has +# saved the current game. +#============================================================================== + +$imported = {} unless $imported +$imported[:ATS_SpecialMessageCodes] = true + +#============================================================================== +# ** Game_ATS +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# new public instance variables - message_speed; special_message_codes +#============================================================================== + +class Game_ATS + CONFIG = {} unless $imported[:AdvancedTextSystem] + CONFIG[:special_message_codes] = { + special_message_codes: true, + #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + # EDITABLE REGION + #|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + # :message_speed => x, + # Changing the value of x determines the default number of frames to + # wait between drawing letters in a message window. 0 is instant. + :message_speed => 1, + # :show_fast_speed => x + # Changing the value of x determines the number of frames to wait + # between drawing letters when the player is holding enter. 0 is + # instant + :show_fast_speed => 0, + # Font Settings - Set the default settings for the font you are using here. + :font_name => Font.default_name, # String or Array of Strings + :font_size => Font.default_size, # Integer + :font_bold => Font.default_bold, # true or false + :font_italic => Font.default_italic, # true or false + :font_outline => Font.default_outline, # true or false + :font_shadow => Font.default_shadow, # true or false + :font_underline => false, # true or false + :font_highlight => -1, # Integer for colour of highlight + #|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + # END EDITABLE REGION + #//////////////////////////////////////////////////////////////////////// + } + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Public Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CONFIG[:special_message_codes].keys.each { |key| attr_accessor key } +end + +#============================================================================== +# Initialize Common ATS Data if no other ATS script interpreted first +#============================================================================== + +if !$imported[:AdvancedTextSystem] + #============================================================================ + # *** DataManager + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Summary of Changes: + # aliased method - create_game_objects; make_save_contents; + # extract_save_contents + #============================================================================ + module DataManager + class << self + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Create Game Objects + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias modb_ats_crtgmobj_6yh7 create_game_objects + def create_game_objects(*args, &block) + modb_ats_crtgmobj_6yh7(*args, &block) + $game_ats = Game_ATS.new + $game_ats.init_new_installs + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Make Save Contents + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mlba_ats_mksave_5tg9 make_save_contents + def make_save_contents(*args, &block) + contents = mlba_ats_mksave_5tg9(*args, &block) + contents[:ats] = $game_ats + contents + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Extract Save Contents + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias ma_ats_extrcsvcon_8uj2 extract_save_contents + def extract_save_contents(contents, *args, &block) + ma_ats_extrcsvcon_8uj2(contents, *args, &block) + $game_ats = contents[:ats] ? contents[:ats] : Game_ATS.new + $game_ats.init_new_installs + end + end + end + + #============================================================================ + # ** Game_ATS + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # This class holds the default data for all scripts in the ATS series + #============================================================================ + + class Game_ATS + def initialize; reset; end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Reset any or all installed ATS scripts + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def reset(script_name = nil) + if script_name.is_a? (Symbol) # If script to reset specified + CONFIG[script_name].each_pair { |key, value| + self.send("#{key}=".to_sym, value) + $game_message.send("#{key}=".to_sym, value) + } + else # Reset all ATS scripts + CONFIG.keys.each { |script| reset(script) } + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Initialize any newly installed ATS scripts + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def init_new_installs + CONFIG.keys.each { |script| reset(script) unless self.send(script) } + end + end + + #============================================================================ + # ** Game_Message + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Summary of Changes: + # aliased method - clear + #============================================================================ + + class Game_Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Clear + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mlb_ats_clrats_5tv1 clear + def clear(*args, &block) + mlb_ats_clrats_5tv1(*args, &block) # Run Original Method + return if !$game_ats + Game_ATS::CONFIG.values.each { |installed| + installed.keys.each { |key| self.send("#{key}=".to_sym, $game_ats.send(key)) } + } + end + end + + #============================================================================ + # ** Game_Interpreter + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Summary of Changes: + # new methods - ats_all; ats_next + #============================================================================ + + class Game_Interpreter + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * ATS All + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def ats_all(sym, *args, &block) + $game_ats.send("#{sym}=".to_sym, *args, &block) + ats_next(sym, *args, &block) + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * ATS Next + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def ats_next(sym, *args, &block) + $game_message.send("#{sym}=".to_sym, *args, &block) + end + end +end + +$imported[:AdvancedTextSystem] = true + +class Game_Message + Game_ATS::CONFIG[:special_message_codes].keys.each { |key| attr_accessor key } +end + +#============================================================================== +# ** Game_Event +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# new method - name +#============================================================================== + +class Game_Event + def name + return @event ? @event.name : "" + end +end + +#============================================================================== +# ** Window_Base +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# aliased methods - initialize; convert_escape_characters; calc_line_height; +# process_escape_character; process_normal_character +#============================================================================== + +class Window_Base + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Object Initialization + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mlgb_atssmc_init_3do9 initialize + def initialize(*args, &block) + mlgb_atssmc_init_3do9(*args, &block) # Run Original Method + @underline = false + @highlight = -1 + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Convert Escape Characters + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias ma_atssmc_convesc_4rc1 convert_escape_characters + def convert_escape_characters(text, *args, &block) + result = ma_atssmc_convesc_4rc1(text, *args, &block) # Run Original Method + # Party + result.gsub!(/\ePID\[(\d+)\]/i) { $1.to_i >= 1 ? $game_party.members[$1.to_i - 1].id.to_s : "1" } + result.gsub!(/\ePG/i) { $game_party.gold } + # Message Speed + result.gsub!(/\eS\[([\+-]?\d+)\]/i) { "\eMS\[#{$1}\]" } + result.gsub!(/\e%/) { "\eDS%"} + # Hex Colour + result.gsub!(/\eC\[#/i) { "\eHC[" } + # Icons + result.gsub!(/\eII\[(\d+)\]/i) { "\eI\[#{$data_items[$1.to_i].icon_index}\]" rescue "" } + result.gsub!(/\eIW\[(\d+)\]/i) { "\eI\[#{$data_weapons[$1.to_i].icon_index}\]" rescue "" } + result.gsub!(/\eIA\[(\d+)\]/i) { "\eI\[#{$data_armors[$1.to_i].icon_index}\]" rescue "" } + result.gsub!(/\eIS\[(\d+)\]/i) { "\eI\[#{$data_skills[$1.to_i].icon_index}\]" rescue "" } + result.gsub!(/\eIT\[(\d+)\]/i) { "\eI\[#{$data_states[$1.to_i].icon_index}\]" rescue "" } + # Actor Stats + result.gsub!(/\eAC\[(\d+)\]/i) { $data_classes[$game_actors[$1.to_i].class_id].name } + result.gsub!(/\eAL\[(\d+)\]/i) { $game_actors[$1.to_i].level.to_s } + # Names + result.gsub!(/\eMAP\[(\d+)\]/i) { load_data(sprintf("Data/Map%03d.rvdata2", $1.to_i)).display_name rescue "" } + result.gsub!(/\eMAP/i) { $game_map.display_name rescue "" } + result.gsub!(/\eNP\[(\d+)\]/i) { $game_party.members[$1.to_i].name rescue "" } + result.gsub!(/\eNE\[(\d+)\]/i) { $game_map.events[$1.to_i].name rescue "" } + result.gsub!(/\eNN\[(\d+)\]/i) { $game_actors[$1.to_i].nickname rescue "" } + result.gsub!(/\eNI\[(\d+)\]/i) { $data_items[$1.to_i].name rescue "" } + result.gsub!(/\eNW\[(\d+)\]/i) { $data_weapons[$1.to_i].name rescue "" } + result.gsub!(/\eNA\[(\d+)\]/i) { $data_armors[$1.to_i].name rescue "" } + result.gsub!(/\eNS\[(\d+)\]/i) { $data_skills[$1.to_i].name rescue "" } + result.gsub!(/\eNT\[(\d+)\]/i) { $data_states[$1.to_i].name rescue "" } + result.gsub!(/\eNM\[(\d+)\]/i) { $data_enemies[$1.to_i].name rescue "" } + result.gsub!(/\eNC\[(\d+)\]/i) { $data_classes[$1.to_i].name rescue "" } + result.gsub!(/\eNV\[(\d+)\]/i) { $data_system.variables[$1.to_i] } + result.gsub!(/\eNSW\[(\d+)\]/i) { $data_system.switches[$1.to_i] } + result.gsub!(/\eNWT\[(\d+)\]/i) { $data_system.weapon_types[$1.to_i] } + result.gsub!(/\eNAT\[(\d+)\]/i) { $data_system.armor_types[$1.to_i] } + result.gsub!(/\eNST\[(\d+)\]/i) { $data_system.skill_types[$1.to_i] } + result.gsub!(/\eNL\[(\d+)\]/i) { $data_system.elements[$1.to_i] } + # Vocab + result.gsub!(/\eVOCAB\[(\w+),\s*(\d+)\s*\]/i) { Vocab.send($1.downcase, $2.to_i) rescue "" } + result.gsub!(/\eVOCAB\[(\w+)\]/i) { Vocab.send($1.downcase) rescue "" } + # Font Settings + result.gsub!(/\eB/i) { "\eFE[0]" } # Bold On + result.gsub!(/\/B/i) { "\eFE[1]" } # Bold Off + result.gsub!(/\eI([^\[])/i) { "\eFE[2]#{$1}" } # Italics On + result.gsub!(/\/I/i) { "\eFE[3]" } # Italics Off + result.gsub!(/\eO/i) { "\eFE[4]" } # Outline On + result.gsub!(/\/O/i) { "\eFE[5]" } # Outline Off + result.gsub!(/\eS([^\[!])/i) { "\eFE[6]#{$1}" } # Shadow On + result.gsub!(/\/S/i) { "\eFE[7]" } # Shadow Off + result.gsub!(/\eU/i) { "\eFE[8]" } # Underline On + result.gsub!(/\/U/i) { "\e\FE[9]" } # Underline Off + result.gsub!(/\eHL\[(\d+)\]/i) { "\eHL[#{$1.to_i}]" } # Highlight On: custom colour + result.gsub!(/\eHL([^\[])/i) { "\eHL[17]#{$1}" }# Highlight On: system colour + result.gsub!(/\/HL/i) { "\eHL[0]" } # Hightlight Off + result.gsub!(/\/FN/i) { "\eFN[]" } # Default Font + result.gsub!(/\/FS/i) { "\eFS[#{Font.default_size}]" } # Default Font + # Conditional Text + result.gsub!(/\eS\[(\d+)[,;:](.+?)\]/mi) { $game_switches[$1.to_i] ? $2 : "" } + result.gsub!(/\eS!\[(\d+)[,;:](.+?)\]/mi) { $game_switches[$1.to_i] ? "" : $2 } + result.gsub!(/\eS\{(\d+)[,;:](.+?)\}/mi) { $game_switches[$1.to_i] ? $2 : "" } + result.gsub!(/\eS!\{(\d+)[,;:](.+?)\}/mi) { $game_switches[$1.to_i] ? "" : $2 } + # Evaluation + result.gsub!(/\e#\{(.+?)\}/im) { (eval($1)).to_s rescue "" } + # Do resubstitutions if requested + if result.sub!(/\eRESUB/i, "") != nil + result = convert_escape_characters(result, *args, &block) + end + result.gsub!(/\e(N|LB)/i, "\n") # Line Break + result # Return result + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Calculate Line Height + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias ma_atssmc_clclnhght_3ek9 calc_line_height + def calc_line_height(text, *args, &block) + res = ma_atssmc_clclnhght_3ek9(text, *args, &block) # Run Original Method + text.slice(/^.*$/).scan(/\eFS\[(\d+)\]/i) { |num| res = [res, num[0].to_i].max } + res + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Process Normal Character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mlra_atssmc_procnorm_5fg2 process_normal_character + def process_normal_character(c, pos, *args, &block) + draw_highlight_underline(c, pos) if @underline || @highlight + mlra_atssmc_procnorm_5fg2(c, pos, *args, &block) # Run Original Method + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Process Escape Character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias ma_atssmc_procesc_7gv1 process_escape_character + def process_escape_character(code, text, pos, *args, &block) + case code.upcase + when 'X' then pos[:x] = obtain_escape_param(text) + when 'HC' # Hex Colour + hex = text.slice!(/^\[([ABCDEF\d]+)\]/i) + rgb = $1.scan(/../).map { |hex| hex.to_i(16) } + change_color(Color.new(*rgb)) if rgb.size.between?(3,4) + when 'FE' # Font Settings + param = obtain_escape_param(text) + case param / 2 + when 0 then contents.font.bold = ((param % 2) == 0) # Bold + when 1 then contents.font.italic = ((param % 2) == 0) # Italic + when 2 then contents.font.outline = ((param % 2) == 0) # Outline + when 3 then contents.font.shadow = ((param % 2) == 0) # Shadow + when 4 then @underline = ((param % 2) == 0) # Underline + end + when 'HL' then @highlight = obtain_escape_param(text) - 1 # Highlight + when 'FN' # Font Name + text.slice!(/^\[(.*?\])/) + param = $1 + if param.nil? || param == ']' + contents.font.name = Font.default_name + else + ary = [] + while param.slice!(/\s*(.+?)\s*[,;:\]]/) != nil do ary.push($1) end + contents.font.name = ary + end + when 'FS' # Font Size + text.slice!(/^\[(\d+)\]/) + contents.font.size = [[$1.to_i, 16].max, 64].min + else + ma_atssmc_procesc_7gv1(code, text, pos, *args, &block) # Run Original Method + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Draw Highlighting and Underlining + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def draw_highlight_underline(c, pos) + text_width = text_size(c).width + y_space = (pos[:height] - contents.font.size) / 2 + if @underline # Underlining + y = pos[:y] + pos[:height] + y -= [2, y_space].max + contents.fill_rect(pos[:x], y, text_width, 2, contents.font.color) + end + if @highlight >= 0 # Highlighting + y = pos[:y] + y += y_space - 1 if y_space > 0 + text_height = [contents.font.size + 2, pos[:height]].min + colour = text_color(@highlight) + colour.alpha = 128 if colour.alpha > 128 + contents.fill_rect(pos[:x], y, text_width, text_height, colour) + end + end +end + +#=============================================================================== +# ** Window_Message +#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Summary of Changes: +# aliased method - clear_instance_variables; process_escape_character; +# update_show_fast; wait_for_one_character +# overwritten super method - reset_font_settings +#=============================================================================== + +class Window_Message + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Clear Instance Variables + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias ma_atssmc_clrinsvar_3dq9 clear_instance_variables + def clear_instance_variables(*args, &block) + ma_atssmc_clrinsvar_3dq9(*args, &block) # Run Original Method + @skip_disabled = false + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Reset Font Settings + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def reset_font_settings(*args, &block) + change_color(normal_color) + contents.font.name = $game_message.font_name + contents.font.size = $game_message.font_size + contents.font.bold = $game_message.font_bold + contents.font.italic = $game_message.font_italic + contents.font.outline = $game_message.font_outline + contents.font.shadow = $game_message.font_shadow + @underline = $game_message.font_underline + @highlight = $game_message.font_highlight + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Process Escape Character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mnaa_atssmc_escchar_3dr9 process_escape_character + def process_escape_character(code, text, pos, *args, &block) + case code.upcase + when 'MS' # Set Message Speed + if !text[/^\[([+-])/].nil? + text.sub!(/^\[([\+-])/, '[' ) + sign = $1 + else + sign = "" + end + param = obtain_escape_param(text) + sign.empty? ? $game_message.message_speed = param : $game_message.message_speed += "#{sign}#{param}".to_i + when 'DS' + text.slice!(/^%/) # Delete buffer to ensure code isn't overextended + @skip_disabled = !@skip_disabled + when 'W' then wait(obtain_escape_param(text)) + else + mnaa_atssmc_escchar_3dr9(code, text, pos, *args, &block) # Run Original Method + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Update Show Fast + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mlg_atssmc_updshwfast_7yb1 update_show_fast + def update_show_fast(*args, &block) + mlg_atssmc_updshwfast_7yb1(*args, &block) + if $game_message.message_speed < 1 + @show_fast = true + elsif @skip_disabled + @show_fast = false + elsif $game_message.show_fast_speed > 0 + @show_fast = Input.press?(:C) + end + end + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # * Wait for One Character + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + alias mdrnl_smcats_waitonechar_5rf1 wait_for_one_character + def wait_for_one_character(*args, &block) + mdrnl_smcats_waitonechar_5rf1(*args, &block) # Run Original Method + if (@show_fast || @line_show_fast) && $game_message.show_fast_speed > 0 + wait($game_message.show_fast_speed) + elsif $game_message.message_speed > 1 + wait($game_message.message_speed - 1) + end + end +end diff --git a/Kawariki-patches/ports/BasicMouse.rb b/Kawariki-patches/ports/BasicMouse.rb new file mode 100644 index 0000000..1d3093f --- /dev/null +++ b/Kawariki-patches/ports/BasicMouse.rb @@ -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 diff --git a/Kawariki-patches/ports/CRDJ_Input.rb b/Kawariki-patches/ports/CRDJ_Input.rb new file mode 100644 index 0000000..faa7384 --- /dev/null +++ b/Kawariki-patches/ports/CRDJ_Input.rb @@ -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 diff --git a/Kawariki-patches/ports/Glitchfinder_Keyboard_Stub.rb b/Kawariki-patches/ports/Glitchfinder_Keyboard_Stub.rb new file mode 100644 index 0000000..9130b2c --- /dev/null +++ b/Kawariki-patches/ports/Glitchfinder_Keyboard_Stub.rb @@ -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 \ No newline at end of file diff --git a/Kawariki-patches/ports/Mouse.rb b/Kawariki-patches/ports/Mouse.rb new file mode 100644 index 0000000..03bbe79 --- /dev/null +++ b/Kawariki-patches/ports/Mouse.rb @@ -0,0 +1,1052 @@ +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# v1.10 by Shaz, mkxp port by Taeyeon Mori +#---------------------------------------------------------------------------- +# This is a conversion of the XP Mouse script by Near Fantastica and +# SephirothSpawn modified by Amaranth Games, to run under VX Ace. +#---------------------------------------------------------------------------- +# To Install: +# Copy and paste into a new slot in materials, below all other scripts +#---------------------------------------------------------------------------- +# To Customize: +# Add keyword icon index pairs to the ICON hash (below this documentation). +# Each of the keywords can be used in an event comment to make the mouse +# cursor change into that icon when hovering over the event. +#---------------------------------------------------------------------------- +# To Use: +# Add the following comment to an event page: +# +# where icon is the keyword from the ICON hash below +# x and y are the offsets to override player movement (optional) +# name is the text to display next to the icon when hovering over the event (optional) +# +# Examples: +# +# will change the cursor into the 'fight' icon when over the event +# +# will change the cursor into the 'touch' icon when over the event, and +# make the player walk to the tile below the event when the mouse button is +# clicked +# +# will change the cursor into the 'talk' icon and display the name Gloria +# +# will change the cursor into the 'talk' icon and display the name Henry Smith, +# and when the mouse button is clicked, the player will walk to the tile +# two below the event (good to use for shops where there's a counter in between) +# +# To force pathfinding on the player or an event, simply add a move route with +# the player or event as the subject, with a Script command, and call +# find_path(x, y) where x and y are the coordinates of the tile you want to move to +# Examples: +# Set Move Route (Player): Script: find_path(5, 8) +# will make the player find a path to tile 5, 8 +# Set Move Route (This Event): Script: find_path(10, 5) +# will make the event find a path to tile 10, 5 +# +# NOTE: The path will be ATTEMPTED. If there is no path TO that exact tile, +# a path to an adjacent tile will be attempted. If no path is found there +# either, no movement will occur. +# If a route is found, the player or event will begin moving towards it. But +# if their path is blocked while they are moving, movement will be cancelled. +#---------------------------------------------------------------------------- +# Author's Notes: +# This script should work with any RTP script. +# I do not guarantee that it will work with ANY other script (especially anything +# that overrides player or event movement, such as pixel movement scripts, or +# custom window scripts). +# +# Script OVERWRITES the following methods: +# Game_Map.setup_starting_map_event +# Game_Map.setup_autorun_common_event +# +# If you have other scripts that ALIAS these methods, this mouse script should +# be placed above them. +#---------------------------------------------------------------------------- +# Terms: +# Use in free and commercial games +# Credit: Near Fantastica, SephirothSpawn, Amaranth Games, Shaz +#---------------------------------------------------------------------------- +# Versions: +# 1.0 - 6 Sept 2013 - initial release +# 1.02 - 7 Sept 2013 - fixed crash when loading games saved prior to adding script +# - fixed player gets stuck on impassable area on world map +# when clicking while leaving air ship +# 1.03 - 8 Sept 2013 - fixed actor moving to diagonal tile instead of adjacent +# 1.04 - 10 Sept 2013 - fixed vehicle pathfinding on world map +# - fixed event trigger when no path found +# 1.05 - 14 Sept 2013 - tweaked accessing of tilemap offset +# 1.06 - 3 Nov 2013 - disabled mouse movement when waiting for NPC move route +# - fixed events not triggering after player finishes walking +# 1.07 - 6 Nov 2013 - slow down mouse scrolling, and don't loop save files +# 1.08 - 24 Nov 2013 - cater for YEA Core large resolution with too-small maps +# - fixed early event activation bug introduced in 1.06 +# - replaced calc of Windows_Selectable boundaries with item_rect +# - added ability to completely disable mouse +# 1.09 - 21 Dec 2013 - fixed mouse re-enable when calling common events +# 1.10 - 6 Apr 2014 - add interaction for top part of > 32pixel high event +# - activate an event without walking up to it +# (add comment at top of event page) +# - arrow keys override mouse movement when pathfinding +# - ignore mouse in menus when using keyboard +# - make player walk to counter opposite shopkeepers +# kk1 - 5 Jan 2022 - port to mkxp-z +#============================================================================ + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Mouse Sprite +#============================================================================ + + +# Add/remove/change icon names here. The icon name is what will be used in the +# event command to show a different mouse icon when hovering over +# the event. These MUST be in lower case here! +ICON = {'arrow' => 528, 'talk' => 4, 'look' => 3, 'fight' => 116, + 'touch' => 491, 'exit' => 121, 'work' => 560, 'workshowel' => 576} +DEFAULT_ICON = 'arrow' + +class Sprite_Mouse < Sprite + #-------------------------------------------------------------------------- + # * Initialization + #-------------------------------------------------------------------------- + def initialize + super + self.z = 10100 + self.ox = 4 + update + @dummy = Bitmap.new(32, 32) + self.bitmap = Bitmap.new(32, 32) + @enabled = true + @ignored = false + end + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + def update + return if !@enabled + super + if !SceneManager.scene.nil? + if !Mouse.position.nil? + mx, my = *Mouse.position + if @cursor == DEFAULT_ICON + self.x = mx unless mx.nil? + else + self.x = [mx, Graphics.width - self.bitmap.width].min unless mx.nil? + end + self.y = my unless my.nil? + end + if @scene != SceneManager.scene.class || Mouse.trigger? + @scene = SceneManager.scene.class + set_bitmap + end + end + end + #-------------------------------------------------------------------------- + # * Set Bitmap + #-------------------------------------------------------------------------- + def set_bitmap(cursor = DEFAULT_ICON, text = nil) + if @ignored + cursor = DEFAULT_ICON + text = nil + end + + if @cursor != cursor || @text != text + @cursor = cursor + @text = text + item_cursor = ICON[cursor] + rect = Rect.new(item_cursor % 16 * 24, item_cursor / 16 * 24, 24, 24) + if @text.nil? + self.bitmap = Bitmap.new(24, 32) + self.bitmap.blt(0, 0, Cache.system('Iconset'), rect) + else + w = @dummy.text_size(@text).width + h = @dummy.font.size + bitmap = Bitmap.new(26 + w, [32, h+2].max) + bitmap.font.size = @dummy.font.size + bitmap.font.shadow = true + if self.x + 26 + w > Graphics.width + bitmap.draw_text(0, 0, w, h, @text) + bitmap.blt(w, 0, Cache.system('Iconset'), rect) + else + bitmap.blt(0, 0, Cache.system('Iconset'), rect) + bitmap.draw_text(26, 0, w, h, @text) + end + self.bitmap = bitmap + end + end + end + #-------------------------------------------------------------------------- + # * Update Event Cursors + #-------------------------------------------------------------------------- + def update_event_cursors + # Remove mouse icon and text if we're off the grid + if Mouse.grid.nil? + set_bitmap + return + end + # Set cursor and text according to event + x, y = *Mouse.grid + event = $game_map.lowest_mouse_event_xy(x, y) + unless event.nil? && y < 410 + if !event.mouse_icon.nil? || !event.mouse_text.nil? + set_bitmap(event.mouse_icon, event.mouse_text) + return + end + end + # default bitmap if not over an event + set_bitmap + end + #-------------------------------------------------------------------------- + # * Enable Mouse + #-------------------------------------------------------------------------- + def enabled=(value) + @enabled = value + self.visible = value + end + #-------------------------------------------------------------------------- + # * Mouse Enabled? + #-------------------------------------------------------------------------- + def enabled? + @enabled + end + #-------------------------------------------------------------------------- + # * Ignore Mouse + #-------------------------------------------------------------------------- + def ignored=(value) + @ignored = value + end + #-------------------------------------------------------------------------- + # * Mouse Ignored? + #-------------------------------------------------------------------------- + def ignored? + @ignored + end +end + +$mouse = Sprite_Mouse.new + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Mouse Module +#============================================================================ + + +#============================================================================== +# ** Mouse Module +#------------------------------------------------------------------------------ +# by Near Fantastica and SephirothSpawn +# adapted and converted to VX Ace by Shaz +#============================================================================== +module Mouse + #-------------------------------------------------------------------------- + # * Mouse to Input Triggers + # key => Input::KeyCONSTANT (key: 0 - left, 1 - middle, 2 - right) + #-------------------------------------------------------------------------- + Mouse_to_Input_Triggers = {0 => Input::C, 1 => Input::B, 2 => Input::A} + #-------------------------------------------------------------------------- + # * Module Variables + #-------------------------------------------------------------------------- + @old_pos = 0 + @sys_cursor_visible = true + #-------------------------------------------------------------------------- + # * Mouse Grid Position + #-------------------------------------------------------------------------- + def self.grid + return nil if @pos.nil? + return nil if !SceneManager.scene_is?(Scene_Map) + mx, my = SceneManager.scene.instance_variable_get(:@spriteset).tilemap_offset + x = (@pos[0] + mx) / 32 + y = (@pos[1] + my) / 32 + return [x, y] + end + #-------------------------------------------------------------------------- + # * Mouse Position + #-------------------------------------------------------------------------- + def self.position + return @pos.nil? ? [0, 0] : @pos + end + #-------------------------------------------------------------------------- + # * Mouse Position + #-------------------------------------------------------------------------- + def self.pos + return Input.mouse_x, Input.mouse_y + end + #-------------------------------------------------------------------------- + # * Update Mouse Position + #-------------------------------------------------------------------------- + def self.update + old_pos = @pos + @pos = self.pos + + # Has mouse been moved? + if old_pos != @pos + Input.method = :mouse + end + + # Which mouse to show - custom, or system? + if $mouse.enabled? == @sys_cursor_visible + @sys_cursor_visible = !@sys_cursor_visible + Graphics.show_cursor = @sys_cursor_visible + end + end + #-------------------------------------------------------------------------- + # * Trigger? + # id : 0:Left, 1:Right, 2:Center + #-------------------------------------------------------------------------- + def self.trigger?(id = 0) + return Input.trigger? [:MOUSELEFT, :MOUSERIGHT, :MOUSEMIDDLE][id] + end + #-------------------------------------------------------------------------- + # * Repeat? + # id : 0:Left, 1:Right, 2:Center + #-------------------------------------------------------------------------- + def self.repeat?(id = 0) + return Input.repeat? [:MOUSELEFT, :MOUSERIGHT, :MOUSEMIDDLE][id] + end + #-------------------------------------------------------------------------- + # * Client Size + #-------------------------------------------------------------------------- + def self.client_size + return Graphics.width, Graphics.height + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Input +#============================================================================ + + +class << Input + #-------------------------------------------------------------------------- + # * Public Instance Variables + #-------------------------------------------------------------------------- + attr_accessor :method + #-------------------------------------------------------------------------- + # * Alias Listings + #-------------------------------------------------------------------------- + alias :seph_mouse_input_update :update + alias :seph_mouse_input_trigger? :trigger? + alias :seph_mouse_input_repeat? :repeat? + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + def update + $mouse.update + Mouse.update + seph_mouse_input_update + # Are we using the mouse or the keyboard? + @method = :keyboard if dir4 != 0 || dir8 != 0 + end + #-------------------------------------------------------------------------- + # * Trigger? Test + #-------------------------------------------------------------------------- + def trigger?(constant) + return true if seph_mouse_input_trigger?(constant) + if $mouse.enabled? && !Mouse.pos.nil? + if Mouse::Mouse_to_Input_Triggers.has_value?(constant) + return true if Mouse.trigger?(Mouse::Mouse_to_Input_Triggers.key(constant)) + end + end + return false + end + #-------------------------------------------------------------------------- + # * Repeat? Test + #-------------------------------------------------------------------------- + def repeat?(constant) + return true if seph_mouse_input_repeat?(constant) + if $mouse.enabled? && !Mouse.pos.nil? + if Mouse::Mouse_to_Input_Triggers.has_value?(constant) + return true if Mouse.repeat?(Mouse::Mouse_to_Input_Triggers.key(constant)) + end + end + return false + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Map +#============================================================================ + + +class Spriteset_Map + #-------------------------------------------------------------------------- + # * Tilemap Offset + #-------------------------------------------------------------------------- + def tilemap_offset + if $imported && $imported["YEA-CoreEngine"] + [@tilemap.ox - @viewport1.rect.x, @tilemap.oy - @viewport1.rect.y] + else + [@tilemap.ox, @tilemap.oy] + end + end +end + +class Game_Map + #-------------------------------------------------------------------------- + # * Detect/Set Up Starting Map Event + #-------------------------------------------------------------------------- + def setup_starting_map_event + event = @events.values.find {|event| event.starting } + event.clear_starting_flag if event + @interpreter.setup(event.list, event.id, event.trigger_in?([0,1,2,3])) if event + event + end + #-------------------------------------------------------------------------- + # * Detect/Set Up Autorun Common Event + #-------------------------------------------------------------------------- + def setup_autorun_common_event + event = $data_common_events.find do |event| + event && event.autorun? && $game_switches[event.switch_id] + end + @interpreter.setup(event.list, 0, true) if event + event + end + #-------------------------------------------------------------------------- + # * Get ID of Lowest Mouse-enabled Event at Designated Coordinates + #-------------------------------------------------------------------------- + def lowest_mouse_event_xy(x, y) + list = events_xy(x, y) + events_xy(x, y+1) + list.sort! {|a, b| b.y - a.y} + evt = nil + list.each do |event| + if (event.pos?(x, y) || (event.pos?(x, y+1) && event.height > 32)) && + (evt.nil? || event.y > evt.y) + evt = event + break + end + end + return evt + end +end + +class Scene_Map + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + alias shaz_mouse_scene_map_update update + def update + $mouse.update_event_cursors + shaz_mouse_scene_map_update + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Event +#============================================================================ + + +module RPG + class Event + class Page + #-------------------------------------------------------------------- + # * Public Instance Variables + #-------------------------------------------------------------------- + attr_reader :mouse_icon + attr_reader :mouse_text + attr_reader :mouse_position + attr_reader :mouse_autoactivate + #-------------------------------------------------------------------- + # * Build Stats + #-------------------------------------------------------------------- + def build_stats + # Mouse icons (icon mandatory, others optional) + # + @mouse_icon = nil + @mouse_text = nil + @mouse_position = [0, 0] + @mouse_autoactivate = false + # look for mouse instructions + list.each do |command| + if [108, 408].include?(command.code) + comment = command.parameters[0] + case comment + when //i.match(comment)[1].split(' ') + @mouse_icon = params.shift + if params.size > 1 && params[0] =~ /\d+/ && params[1] =~ /\d+/ + @mouse_position = [params.shift.to_i, params.shift.to_i] + end + if params.size > 0 + @mouse_text = params.join(' ') + end + when // + @mouse_autoactivate = true + end + end #if + end #do + end #def + end + end +end + +class Game_Event < Game_Character + #-------------------------------------------------------------------------- + # * Public Instance Variables + #-------------------------------------------------------------------------- + attr_reader :mouse_icon + attr_reader :mouse_text + attr_reader :mouse_position + attr_reader :mouse_autoactivate + #-------------------------------------------------------------------------- + # * Start Event + #-------------------------------------------------------------------------- + alias shaz_mouse_game_event_start start + def start + $game_player.start_event(@id) if !empty? + shaz_mouse_game_event_start + end + #-------------------------------------------------------------------------- + # * Clear Event Page Settings + #-------------------------------------------------------------------------- + alias shaz_mouse_game_event_clear_page_settings clear_page_settings + def clear_page_settings + shaz_mouse_game_event_clear_page_settings + @mouse_icon = nil + @mouse_text = nil + @mouse_position = [0, 0] + @mouse_autoactivate = false + @height = 0 + end + #-------------------------------------------------------------------------- + # * Set Up Event Page Settings + #-------------------------------------------------------------------------- + alias shaz_mouse_game_event_setup_page_settings setup_page_settings + def setup_page_settings + shaz_mouse_game_event_setup_page_settings + @page.build_stats + @mouse_icon = @page.mouse_icon + @mouse_text = @page.mouse_text + @mouse_position = @page.mouse_position + @mouse_autoactivate = @page.mouse_autoactivate + set_size + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Character +#============================================================================ + + +class Game_CharacterBase + attr_reader :height # Height of character bitmap + #-------------------------------------------------------------------------- + # * Initialize Public Member Variables + #-------------------------------------------------------------------------- + alias shaz_mouse_game_characterbase_init_public_members init_public_members + def init_public_members + shaz_mouse_game_characterbase_init_public_members + @height = 0 + end + #-------------------------------------------------------------------------- + # * Change Graphics + # character_name : new character graphic filename + # character_index : new character graphic index + #-------------------------------------------------------------------------- + alias shaz_mouse_game_characterbase_set_graphic set_graphic + def set_graphic(character_name, character_index) + shaz_mouse_game_characterbase_set_graphic(character_name, character_index) + set_size + end + #-------------------------------------------------------------------------- + # * Set character width/height size + #-------------------------------------------------------------------------- + def set_size + bw = Cache.character(@character_name).width + bh = Cache.character(@character_name).height + sign = @character_name[/^[\!\$]./] + if sign && sign.include?('$') + @width = bw / 3 + @height = bh / 4 + else + @width = bw / 12 + @height = bh / 8 + end + end + #-------------------------------------------------------------------------- + # * Detect Collision with Event + #-------------------------------------------------------------------------- + def collide_with_events?(x, y) + $game_map.events_xy_nt(x, y).any? do |event| + self != event && (event.normal_priority? || self.is_a?(Game_Event)) + end + end + #-------------------------------------------------------------------------- + # * Detect Collision with Vehicle + #-------------------------------------------------------------------------- + def collide_with_vehicles?(x, y) + !self.is_a?(Game_Player) && ($game_map.boat.pos_nt?(x, y) || $game_map.ship.pos_nt?(x, y)) + end + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + alias shaz_mouse_game_characterbase_update update + def update + run_path if @runpath + shaz_mouse_game_characterbase_update + end + #-------------------------------------------------------------------------- + # * Run Path + #-------------------------------------------------------------------------- + def run_path + return if moving? + @step = @map.nil? || @map[@x, @y].nil? ? 0 : @map[@x, @y] - 1 + if @step < 1 + clear_path + else + x, y = @x, @y + dirs = [] + dirs.push(6) if @map[@x+1, @y] == @step && passable?(@x, @y, 6) + dirs.push(2) if @map[@x, @y+1] == @step && passable?(@x, @y, 2) + dirs.push(4) if @map[@x-1, @y] == @step && passable?(@x, @y, 4) + dirs.push(8) if @map[@x, @y-1] == @step && passable?(@x, @y, 8) + while dirs.size > 0 + dir = dirs.delete_at(rand(dirs.size)) + move_straight(dir) + break if x != @x || y != @y + end + # clear the path if we couldn't move + clear_path if x == @x && y == @y + end + end + #-------------------------------------------------------------------------- + # * Find Path + #-------------------------------------------------------------------------- + def find_path(x, y) + sx, sy = @x, @y + @tx, @ty = x, y + result = setup_map(sx, sy) + @runpath = result[0] + @map = result[1] + @map[sx, sy] = result[2] if result[2] != nil + end + #-------------------------------------------------------------------------- + # * Clear Path + #-------------------------------------------------------------------------- + def clear_path + @map = nil + @runpath = false + end + #-------------------------------------------------------------------------- + # * Setup Map + #-------------------------------------------------------------------------- + def setup_map(sx, sy) + map = Table.new($game_map.width, $game_map.height) + update_counter = 0 + map[@tx, @ty] = 1 + old_positions = [[@tx, @ty]] + new_positions = [] + + # if tile is impassable, but CAN move to adjacent tiles, use the adjacent tiles instead + if (!passable?(@tx, @ty, 2) && !passable?(@tx, @ty, 4) && + !passable?(@tx, @ty, 6) && !passable?(@tx, @ty, 8)) || + $game_map.events_xy_nt(@tx, @ty).any? { |evt| evt.normal_priority? && evt != self } + old_positions = [] + + # Can we move from the destination tile in any direction? + if map_passable?(@tx, @ty, 2) + map[@tx, @ty+1] = 1 + old_positions.push([@tx, @ty+1]) + end + if map_passable?(@tx, @ty, 8) + map[@tx, @ty-1] = 1 + old_positions.push([@tx, @ty-1]) + end + if map_passable?(@tx, @ty, 4) + map[@tx-1, @ty] = 1 + old_positions.push([@tx-1, @ty]) + end + if map_passable?(@tx, @ty, 6) + map[@tx+1, @ty] = 1 + old_positions.push([@tx+1, @ty]) + end + + # If not, can we at least move up to the destination tile? + if old_positions.size == 0 + if map_passable?(@tx-1,@ty,6) + map[@tx-1,@ty] = 1 + old_positions.push([@tx-1,@ty]) + end + if map_passable?(@tx+1,@ty,4) + map[@tx+1,@ty] = 1 + old_positions.push([@tx+1,@ty]) + end + if map_passable?(@tx,@ty-1,2) + map[@tx,@ty-1] = 1 + old_positions.push([@tx,@ty-1]) + end + if map_passable?(@tx,@ty+1,8) + map[@tx,@ty+1] = 1 + old_positions.push([@tx,@ty+1]) + end + end + end + + # If there are any counters, can we move to the tile on the other side? + if map_passable?(@tx-2,@ty,6) && $game_map.counter?(@tx-1,@ty) + map[@tx-2,@ty] = 1 + old_positions.push([@tx-2,@ty]) + end + if map_passable?(@tx+2,@ty,4) && $game_map.counter?(@tx+1,@ty) + map[@tx+2,@ty] = 1 + old_positions.push([@tx+2,@ty]) + end + if map_passable?(@tx,@ty-2,2) && $game_map.counter?(@tx,@ty-1) + map[@tx,@ty-2] = 1 + old_positions.push([@tx,@ty-2]) + end + if map_passable?(@tx,@ty+2,2) && $game_map.counter?(@tx,@ty+1) + map[@tx,@ty+2] = 1 + old_positions.push([@tx,@ty+2]) + end + + + depth = 2 + depth.upto(100) { |step| + break if old_positions[0].nil? + @step = step + loop do + break if old_positions[0].nil? + x, y = old_positions.shift + return [true, map, @step-1] if x == sx && y == sy + if map[x, y + 1] == 0 && passable?(x, y, 2) + map[x, y + 1] = @step + new_positions.push([x, y + 1]) + end + if map[x - 1, y] == 0 && passable?(x, y, 4) + map[x - 1, y] = @step + new_positions.push([x - 1, y]) + end + if map[x + 1, y] == 0 && passable?(x, y, 6) + map[x + 1, y] = @step + new_positions.push([x + 1, y]) + end + if map[x, y - 1] == 0 && passable?(x, y, 8) + map[x, y - 1] = @step + new_positions.push([x, y - 1]) + end + # Update graphics? (to reduce lag) + update_counter += 1 + if update_counter > 50 + Graphics.update + update_counter = 0 + end + end + old_positions = new_positions + new_positions = [] + } + return [false, nil, nil] + end +end + +class Game_Character < Game_CharacterBase + #-------------------------------------------------------------------------- + # * Force Move Route + #-------------------------------------------------------------------------- + alias shaz_mouse_game_character_force_move_route force_move_route + def force_move_route(move_route) + clear_path + shaz_mouse_game_character_force_move_route(move_route) + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Player +#============================================================================ + + +class Game_Player < Game_Character + #-------------------------------------------------------------------------- + # * Trigger Map Event + # triggers : Trigger array + # normal : Is priority set to [Same as Characters] ? + #-------------------------------------------------------------------------- + alias shaz_mouse_game_player_start_map_event start_map_event + def start_map_event(x, y, triggers, normal) + @started_events = [] + shaz_mouse_game_player_start_map_event(x, y, triggers, normal) + end + #-------------------------------------------------------------------------- + # * Start Event + #-------------------------------------------------------------------------- + def start_event(event_id) + @started_events = [] if @started_events.nil? + @started_events.push(event_id) + end + #-------------------------------------------------------------------------- + # * Processing of Movement via Input from Directional Buttons + #-------------------------------------------------------------------------- + alias shaz_mouse_game_player_move_by_input move_by_input + def move_by_input + if Input.dir4 > 0 + clear_path + shaz_mouse_game_player_move_by_input + else + # Move by mouse input + if !$game_message.busy? && !$game_message.visible && !@move_route_forcing && + !@vehicle_getting_on && !@vehicle_getting_off && + Mouse.trigger?(0) && !Mouse.grid.nil? && !$mouse.ignored? + mx, my = *Mouse.grid + # turn in direction + if (@x - mx).abs >= (@y - my).abs + set_direction(@x > mx ? 4 : 6) + else + set_direction(@y > my ? 8 : 2) + end + # find path + @event = $game_map.lowest_mouse_event_xy(mx, my) + if @event.nil? + find_path(mx, my) + elsif @event.mouse_autoactivate + @event.start + @started_events = [] + clear_path + else + find_path(@event.x + @event.mouse_position[0], + @event.y + @event.mouse_position[1]) + end + end + end + end + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + alias shaz_mouse_game_player_update update + def update + shaz_mouse_game_player_update + update_pathfinding if !@event.nil? && !moving? + end + #-------------------------------------------------------------------------- + # * Check event after pathfinding + #-------------------------------------------------------------------------- + def update_pathfinding + if @map.nil? || @map[@x, @y] <= 1 + dir = @x < @event.x ? 6 : @x > @event.x ? 4 : @y < @event.y ? 2 : @y > @event.y ? 8 : 0 + # Face event and trigger it (only if not triggered by start_map_event) + turn_toward_character(@event) if !@event.pos?(@x, @y) + if !@started_events.include?(@event.id) && !@map.nil? && !in_airship? + @event.start + @started_events = [] + end + clear_path + end + end + #-------------------------------------------------------------------------- + # * Clear Path + #-------------------------------------------------------------------------- + def clear_path + @event = nil + super + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Interpreter +#============================================================================ + + +class Game_Interpreter + #-------------------------------------------------------------------------- + # * Event Setup + #-------------------------------------------------------------------------- + alias shaz_mouse_game_interpreter_setup setup + def setup(list, event_id = 0, lock_player = false) + shaz_mouse_game_interpreter_setup(list, event_id) + @lock_player = lock_player + end + #-------------------------------------------------------------------------- + # * Execute + #-------------------------------------------------------------------------- + alias shaz_mouse_game_interpreter_run run + def run + $mouse.ignored = true if @lock_player + shaz_mouse_game_interpreter_run + $mouse.ignored = false if @lock_player + end +end + + + + + + +#============================================================================ +# SUPER SIMPLE MOUSE SCRIPT +# Windows +#============================================================================ + + +class Window_Selectable < Window_Base + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + alias shaz_mouse_window_selectable_update update + def update + shaz_mouse_window_selectable_update + process_mouse_handling if Input.method == :mouse + end + #-------------------------------------------------------------------------- + # * Mouse Movement Processing + #-------------------------------------------------------------------------- + def process_mouse_handling + return unless $mouse.enabled? && cursor_movable? + # Add a delay to prevent too-fast scrolling + @delay = @delay ? @delay + 1 : 0 + return if @delay % 3 > 0 + + mx, my = *Mouse.position + vx = self.viewport ? self.x - self.viewport.ox + self.viewport.rect.x : self.x + vy = self.viewport ? self.y - self.viewport.oy + self.viewport.rect.y : self.y + if mx.between?(vx, vx + self.width) && + my.between?(vy, vy + self.height) + mx -= vx + mx -= padding + my -= vy + my -= padding + my += oy + for i in 0 ... item_max + rect = item_rect(i) + if mx.between?(rect.x, rect.x + rect.width) && + my.between?(rect.y, rect.y + rect.height) + last_index = @index + select(i) + if @index != last_index + Sound.play_cursor + end + break + end + end + end + end +end + +class Window_NameInput < Window_Selectable + #-------------------------------------------------------------------------- + # * Mouse Movement Processing + #-------------------------------------------------------------------------- + def process_mouse_handling + return unless $mouse.enabled? + # Add a delay to prevent too-fast scrolling + @delay = @delay ? @delay + 1 : 0 + return if @delay % 3 > 0 + + mx, my = *Mouse.position + vx = (self.viewport ? self.x - self.viewport.ox + self.viewport.rect.x : self.x) + padding + vy = (self.viewport ? self.y - self.viewport.oy + self.viewport.rect.y : self.y) + padding + if mx.between?(vx, vx + self.width - padding * 2) && + my.between?(vy, vy + self.height - padding * 2) + mx -= vx + my -= vy + x = (mx > 5*32+16 ? mx-16 : mx) / 32 + y = my / line_height + last_index = @index + @index = y * 10 + x + Sound.play_cursor if @index != last_index + end + end +end + +class Scene_File < Scene_MenuBase + #-------------------------------------------------------------------------- + # * Update Cursor + #-------------------------------------------------------------------------- + alias shaz_mouse_scene_file_update_cursor update_cursor + def update_cursor + shaz_mouse_scene_file_update_cursor + process_mouse_handling if Input.method == :mouse + end + #-------------------------------------------------------------------------- + # * Mouse Movement Processing + #-------------------------------------------------------------------------- + def process_mouse_handling + return unless $mouse.enabled? + # Add a delay to prevent too-fast scrolling + @delay = @delay ? @delay + 1 : 0 + return if @delay % 3 > 0 + + mx, my = *Mouse.position + vx = @savefile_viewport.ox + mx + vy = @savefile_viewport.oy + my + last_index = @index + new_index = vy / savefile_height + if @index != new_index + if new_index > @index + cursor_down(false) + else + cursor_up(false) + end + Sound.play_cursor + @savefile_windows[last_index].selected = false + @savefile_windows[@index].selected = true + end + end +end + +#============================================================================== +# ** Game_Player +#------------------------------------------------------------------------------ +# This class handles the player. It includes event starting determinants and +# map scrolling functions. The instance of this class is referenced by +# $game_player. +#============================================================================== + +class Game_Player < Game_Character + #-------------------------------------------------------------------------- + # * Frame Update + #-------------------------------------------------------------------------- + alias amn_shazmouse_gameplayer_mousesupport_update update + def update + amn_shazmouse_gameplayer_mousesupport_update + stop_movement_if_message + end + + def stop_movement_if_message + clear_path if $game_message.busy? || $game_message.visible + end + +end \ No newline at end of file diff --git a/Kawariki-patches/ports/TH_EventTriggerLabels.rb b/Kawariki-patches/ports/TH_EventTriggerLabels.rb new file mode 100644 index 0000000..c260408 --- /dev/null +++ b/Kawariki-patches/ports/TH_EventTriggerLabels.rb @@ -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 \ No newline at end of file diff --git a/Kawariki-patches/ports/TH_SimpleAudioEncryption.rb b/Kawariki-patches/ports/TH_SimpleAudioEncryption.rb new file mode 100644 index 0000000..a481dd2 --- /dev/null +++ b/Kawariki-patches/ports/TH_SimpleAudioEncryption.rb @@ -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 diff --git a/Kawariki-patches/ports/XP_CustomResolution.rb b/Kawariki-patches/ports/XP_CustomResolution.rb new file mode 100644 index 0000000..01c5170 --- /dev/null +++ b/Kawariki-patches/ports/XP_CustomResolution.rb @@ -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 \ No newline at end of file diff --git a/Kawariki-patches/ports/Zeus_Fullscreen++.rb b/Kawariki-patches/ports/Zeus_Fullscreen++.rb new file mode 100644 index 0000000..ac59e7b --- /dev/null +++ b/Kawariki-patches/ports/Zeus_Fullscreen++.rb @@ -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 \ No newline at end of file diff --git a/Kawariki-patches/ports/Zeus_Map_Effects.rb b/Kawariki-patches/ports/Zeus_Map_Effects.rb new file mode 100644 index 0000000..96707f8 --- /dev/null +++ b/Kawariki-patches/ports/Zeus_Map_Effects.rb @@ -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 diff --git a/Kawariki-patches/ports/achievements.rb b/Kawariki-patches/ports/achievements.rb new file mode 100644 index 0000000..b7e9f89 --- /dev/null +++ b/Kawariki-patches/ports/achievements.rb @@ -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 [] 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 diff --git a/Kawariki-patches/ports/bitmap_tktk.rb b/Kawariki-patches/ports/bitmap_tktk.rb new file mode 100644 index 0000000..35d4159 --- /dev/null +++ b/Kawariki-patches/ports/bitmap_tktk.rb @@ -0,0 +1,281 @@ +# coding: utf-8 +=begin += Bitmapクラスの拡張 (DLL版) +RPGツクールXP/VX共用 +Bitmapクラスに機能を追加します。 +- Marshaldump可能に +- PNGファイルとして保存 +- 色調変更 +- モザイク効果 +- 色の反転 +- ぼかし効果 +- マスクを用いた切り抜き +- ブレンディング + +■ 注意 + このスクリプトの他に"tktk_bitmap.dll"(ver0.1.2.6以上)が必要になります。 + +Author:: 半生 +Date:: 2010/12/13 +Version:: 0.1.2.6 +URL:: http://www.tktkgame.com/ + +############################################ + 2010/12/13 ver 0.1.2.6 + dllの名称を"hn_rg_bitmap.dll"から"tktk_bitmap.dll"に変更 + LARGE_BITMAP機能でメモリを確保できなかった場合の処理を追加 + 2010/10/12 ver 0.1.2.5(デンジャラスベータ版) + 大きいサイズのBitmapオブジェクトを機能を試験的に実装(危険) + 2010/03/24 ver 0.1.2.2 + ブレンディング機能関連の軽量化。 + 画像連結系メソッドの分離。 + 2010/03/24 ver 0.1.2.1 + ブレンディング機能関連のバグフィックス + 2010/03/22 ver 0.1.2.0 + 加算合成等のブレンディング機能の追加 +2010/02/07 ver 0.1.1.0 + マーシャル化の処理の一部をDLLに移動 +2010/01/17 ver 0.1.0.0 + dllの名称を"hn_rx_bitmap.dll"から"hn_rg_bitmap.dll"に変更 + モザイク効果・色反転・ぼかし効果の追加 +############################################ +=end + +def dummy_blend_blt(dest_id, x, y, src_id, src_x, src_y, width, height, blend_mode, opacity) + puts "Blending operation started!" + puts "Destination Image ID: #{dest_id}" + puts "Source Image ID: #{src_id}" + puts "Blending Position: (#{x}, #{y})" + puts "Source Image Rectangle: (#{src_x}, #{src_y}, #{width}, #{height})" + puts "Blend Mode: #{blend_mode}" + puts "Opacity: #{opacity}" + puts "Simulating image blending operation..." + # More logic to simulate the blending can go here + puts "Finished blending images with the specified settings." +end + +# Example usage: +# dest_bmp = Object.new +# src_bmp = Object.new +# rect = OpenStruct.new(x: 0, y: 0, width: 100, height: 100) + +# Call the dummy blend function directly without a class +# dummy_blend_blt( +# dest_bmp.object_id, 10, 20, src_bmp.object_id, rect.x, rect.y, rect.width, rect.height, 'multiply', 0.7 +# ) + +module TKTK_Bitmap + LARGE_BITMAP = true # 大容量のBitmap作成機能を使うかどうか + DLL_NAME = 'tktk_bitmap' + + ERROR_ALLOCATE_FAILED = -110002 + + @@png_save = Win32API.new(DLL_NAME, 'PngSaveA', 'p n i i', 'i') + @@blur = Win32API.new(DLL_NAME, 'Blur', 'n i', 'i') + @@change_tone = Win32API.new(DLL_NAME, 'ChangeTone', 'n i i i i', 'i') + @@clip_mask = Win32API.new(DLL_NAME, 'ClipMask', 'n n i i i', 'i') + @@invert = Win32API.new(DLL_NAME, 'InvertColor', 'n', 'i') + @@mosaic = Win32API.new(DLL_NAME, 'Mosaic', 'n i i i i i i', 'i') + @@address = Win32API.new(DLL_NAME, 'GetAddress', 'n', 'n') + @@get_pixel_data = Win32API.new(DLL_NAME, 'GetPixelData', 'n p i', 'i') + @@set_pixel_data = Win32API.new(DLL_NAME, 'SetPixelData', 'n p i', 'i') + @@blend_blt = Win32API.new(DLL_NAME, 'BlendBlt', 'n i i n i i i i i i', 'i') + #@@get_hwnd = Win32API.new(DLL_NAME, 'GetGameHWND', 'v', 'l') + @@change_size = Win32API.new(DLL_NAME, 'ChangeSize', 'n i i', 'i') + module_function + + # PNG形式で保存 + def png_save(bitmap,file_name,compression_level,filter) + return @@png_save.call(file_name, bitmap.object_id, compression_level, filter) + end + + # ぼかし効果 + def blur(bitmap, r = 1) + return @@blur.call(bitmap.object_id, r) + end + + # カラーバランス変更? + def change_tone(bitmap, red = 0, green = 0, blue = 0, simplify = 1) + return @@change_tone.call(bitmap.object_id, red, green, blue, simplify) + end + + # マスクによる画像の切り抜き(アルファとの乗算) + def clip_mask(g_bitmap, m_bitmap, x=0, y=0, outer=0) + return @@clip_mask.call(g_bitmap.object_id, m_bitmap.object_id, x, y, outer) + end + + # 色の反転 + def invert(bitmap) + # $game_self_switches.delete(7,["A"]) + # puts "ggg" + # $game_switches.each_with_index do | + # switch, index| + # # puts "Switch #{index + 1}: #{switch}" + # end + $game_switches[11] = false + # puts $game_switches[8] + # puts $game_switches[9] + # puts $game_switches[10] + # puts $game_switches[11] + # puts $game_switches[12] + + + return 0 + end + + # モザイク効果 + def mosaic(bitmap, msw=5, msh=5) + return self.mosaic_rect(bitmap, bitmap.rect, msw, msh) + end + + # モザイク効果(範囲指定) + def mosaic_rect(bitmap, rect, msw=5, msh=5) + return @@mosaic.call(bitmap.object_id, + rect.x, rect.y, rect.width, rect.height, msw, msh) + end + + # ビットマップデータのアドレスを取得 + def address(bitmap) + return @@address.call(bitmap.object_id) + end + + # ビットマップのバイナリデータを取得 + def get_pixel_data(bitmap) + buffer = "bgra" * bitmap.width * bitmap.height + @@get_pixel_data.call(bitmap.object_id, buffer, buffer.size) + return buffer + end + + # ビットマップのバイナリデータを置き換え + def set_pixel_data(bitmap, data) + return @@set_pixel_data.call(bitmap.object_id, data, data.size) + end + + def blend_blt(dest_bmp, x, y, src_bmp, rect, blend_type=0, opacity=255) + $game_switches[11] = false + + # @@blend_blt.call(dest_bmp.object_id, x, y, src_bmp.object_id, + # rect.x, rect.y, rect.width, rect.height, + # blend_type, opacity) + # return 0 + # dest_bmp.object_id, 10, 20, src_bmp.object_id, rect.x, rect.y, rect.width, rect.height, 'multiply', 0.7 + # dest_bmp.object_id, 10, 20, src_bmp.object_id, rect.x, rect.y, rect.width, rect.height, 'multiply', 0.7 + + end + + + # ビットマップのサイズを変更(危険) + def change_size(bitmap, new_width, new_height) + return -1 if (new_width <=0 or new_height <= 0) + result = @@change_size.call(bitmap.object_id, new_width, new_height) + if result == ERROR_ALLOCATE_FAILED + raise("tktk_bitmap:ERROR ALLOCATE FAILED") + end + return result + end + +end + +class Font + def marshal_dump;end + def marshal_load(obj);end +end +class Bitmap + # PNG圧縮用フィルタ + PNG_NO_FILTERS = 0x00 + PNG_FILTER_NONE = 0x08 + PNG_FILTER_SUB = 0x10 + PNG_FILTER_UP = 0x20 + PNG_FILTER_AVG = 0x40 + PNG_FILTER_PAETH = 0x80 + PNG_ALL_FILTERS = (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | + PNG_FILTER_AVG | PNG_FILTER_PAETH) + + # Marshal_dump + def _dump(limit) + return "" if self.disposed? + data = TKTK_Bitmap.get_pixel_data(self) + [width, height, Zlib::Deflate.deflate(data)].pack("LLa*") # ついでに圧縮 + end + + # Marshal_load + def self._load(str) + if str == "" + b = Bitmap.new(1,1) + b.dispose + return b + end + w, h, zdata = str.unpack("LLa*"); b = new(w, h) + TKTK_Bitmap.set_pixel_data(b, Zlib::Inflate.inflate(zdata)) + return b + end + + def address + TKTK_Bitmap.address(self) + end + + # ぼかし効果 + def blur2(r=1) + TKTK_Bitmap.blur(self, r) + end + + # 色調変更 + def change_tone(red, green, blue, simplify = 1) + TKTK_Bitmap.change_tone(self, red, green, blue, simplify) + end + + # クリッピング + def clip_mask(bitmap, x=0, y=0, outer=0) + TKTK_Bitmap.clip_mask(self, bitmap, x, y, outer) + end + + # 色の反転 + def invert + TKTK_Bitmap.invert(self) + end + + # モザイク効果 + def mosaic(msw=5, msh=5) + TKTK_Bitmap.mosaic(self, msw, msh) + end + + # モザイク効果(領域指定) + def mosaic_rect(rect=self.rect, msw=5, msh=5) + TKTK_Bitmap.mosaic_rect(self, rect, msw, msh) + end + + # ブレンディング + def blend_blt(x, y, src_bmp, rect, blend_type=0, opacity=255) + return if opacity <= 0 + TKTK_Bitmap.blend_blt(self, x, y, src_bmp, rect, blend_type, opacity) + end + + #png形式で保存 + def png_save(outp, level = 9, filter = PNG_NO_FILTERS) + if (TKTK_Bitmap.png_save(self, outp, level, filter) != 0) + raise("Bitmap\#png_save failed") + end + end + + # 大きいサイズのBitmap.newを可能に(危険) + # width * height が大体1073741823位まで + if TKTK_Bitmap::LARGE_BITMAP + class << self + unless method_defined?(:_hn_large_bm__new) + alias :_hn_large_bm__new :new + end + def new(*args) + if args.size == 2 && args[0] * args[1] >= 4194304 + new_width = args[0] + new_height = args[1] + # とりあえず小さなサイズで作成 + bitmap = _hn_large_bm__new(16, 16) + TKTK_Bitmap.change_size(bitmap, new_width, new_height) + return bitmap + else + _hn_large_bm__new(*args) + end + end + end # Bitmap.new + end +end # CLASS Bitmap diff --git a/Kawariki-patches/ports/bitmap_tktkk.rb b/Kawariki-patches/ports/bitmap_tktkk.rb new file mode 100644 index 0000000..c1717d6 --- /dev/null +++ b/Kawariki-patches/ports/bitmap_tktkk.rb @@ -0,0 +1,236 @@ +# coding: utf-8 +=begin += Bitmapクラスの拡張 (DLL版) +RPGツクールXP/VX共用 +Bitmapクラスに機能を追加します。 +- Marshaldump可能に +- PNGファイルとして保存 +- 色調変更 +- モザイク効果 +- 色の反転 +- ぼかし効果 +- マスクを用いた切り抜き +- ブレンディング + +■ 注意 + このスクリプトの他に"tktk_bitmap.dll"(ver0.1.2.6以上)が必要になります。 + +Author:: 半生 +Date:: 2010/12/13 +Version:: 0.1.2.6 +URL:: http://www.tktkgame.com/ + +############################################ + 2010/12/13 ver 0.1.2.6 + dllの名称を"hn_rg_bitmap.dll"から"tktk_bitmap.dll"に変更 + LARGE_BITMAP機能でメモリを確保できなかった場合の処理を追加 + 2010/10/12 ver 0.1.2.5(デンジャラスベータ版) + 大きいサイズのBitmapオブジェクトを機能を試験的に実装(危険) + 2010/03/24 ver 0.1.2.2 + ブレンディング機能関連の軽量化。 + 画像連結系メソッドの分離。 + 2010/03/24 ver 0.1.2.1 + ブレンディング機能関連のバグフィックス + 2010/03/22 ver 0.1.2.0 + 加算合成等のブレンディング機能の追加 +2010/02/07 ver 0.1.1.0 + マーシャル化の処理の一部をDLLに移動 +2010/01/17 ver 0.1.0.0 + dllの名称を"hn_rx_bitmap.dll"から"hn_rg_bitmap.dll"に変更 + モザイク効果・色反転・ぼかし効果の追加 +############################################ +=end + +module TKTK_Bitmap + LARGE_BITMAP = true # 大容量のBitmap作成機能を使うかどうか + DLL_NAME = 'tktk_bitmap' + + ERROR_ALLOCATE_FAILED = -110002 + + @@png_save = Win32API.new(DLL_NAME, 'PngSaveA', 'p n i i', 'i') + @@blur = Win32API.new(DLL_NAME, 'Blur', 'n i', 'i') + @@change_tone = Win32API.new(DLL_NAME, 'ChangeTone', 'n i i i i', 'i') + @@clip_mask = Win32API.new(DLL_NAME, 'ClipMask', 'n n i i i', 'i') + @@invert = Win32API.new(DLL_NAME, 'InvertColor', 'n', 'i') + @@mosaic = Win32API.new(DLL_NAME, 'Mosaic', 'n i i i i i i', 'i') + @@address = Win32API.new(DLL_NAME, 'GetAddress', 'n', 'n') + @@get_pixel_data = Win32API.new(DLL_NAME, 'GetPixelData', 'n p i', 'i') + @@set_pixel_data = Win32API.new(DLL_NAME, 'SetPixelData', 'n p i', 'i') + @@blend_blt = Win32API.new(DLL_NAME, 'BlendBlt', 'n i i n i i i i i i', 'i') + #@@get_hwnd = Win32API.new(DLL_NAME, 'GetGameHWND', 'v', 'l') + @@change_size = Win32API.new(DLL_NAME, 'ChangeSize', 'n i i', 'i') + module_function + + # PNG形式で保存 + def png_save(bitmap,file_name,compression_level,filter) + return file_name, bitmap.object_id, compression_level, filter + end + + # ぼかし効果 + def blur(bitmap, r = 1) + return bitmap.object_id, r + end + + # カラーバランス変更? + def change_tone(bitmap, red = 0, green = 0, blue = 0, simplify = 1) + return bitmap.object_id, red, green, blue, simplify + end + + # マスクによる画像の切り抜き(アルファとの乗算) + def clip_mask(g_bitmap, m_bitmap, x=0, y=0, outer=0) + return g_bitmap.object_id, m_bitmap.object_id, x, y, outer + end + + # 色の反転 + def invert(bitmap) + return bitmap.object_id + end + + # モザイク効果 + def mosaic(bitmap, msw=5, msh=5) + return bitmap, bitmap.rect, msw, msh + end + + # モザイク効果(範囲指定) + def mosaic_rect(bitmap, rect, msw=5, msh=5) + return bitmap.object_id, rect.x, rect.y, rect.width, rect.height, msw, msh + end + + # ビットマップデータのアドレスを取得 + def address(bitmap) + return bitmap.object_id + end + + # ビットマップのバイナリデータを取得 + def get_pixel_data(bitmap) + buffer = "bgra" * bitmap.width * bitmap.height + @@get_pixel_data.call(bitmap.object_id, buffer, buffer.size) + return buffer + end + + # ビットマップのバイナリデータを置き換え + def set_pixel_data(bitmap, data) + return bitmap.object_id, data, data.size + end + + def blend_blt(dest_bmp, x, y, src_bmp, rect, blend_type=0, opacity=255) + dest_bmp.object_id, x, y, src_bmp.object_id, + rect.x, rect.y, rect.width, rect.height, + blend_type, opacity + end + + # ビットマップのサイズを変更(危険) + def change_size(bitmap, new_width, new_height) + return -1 if (new_width <=0 or new_height <= 0) + result = @@change_size.call(bitmap.object_id, new_width, new_height) + if result == ERROR_ALLOCATE_FAILED + raise("tktk_bitmap:ERROR ALLOCATE FAILED") + end + return result + end + +end + +class Font + def marshal_dump;end + def marshal_load(obj);end +end +class Bitmap + # PNG圧縮用フィルタ + PNG_NO_FILTERS = 0x00 + PNG_FILTER_NONE = 0x08 + PNG_FILTER_SUB = 0x10 + PNG_FILTER_UP = 0x20 + PNG_FILTER_AVG = 0x40 + PNG_FILTER_PAETH = 0x80 + PNG_ALL_FILTERS = (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | + PNG_FILTER_AVG | PNG_FILTER_PAETH) + + # Marshal_dump + def _dump(limit) + return "" if self.disposed? + data = TKTK_Bitmap.get_pixel_data(self) + [width, height, Zlib::Deflate.deflate(data)].pack("LLa*") # ついでに圧縮 + end + + # Marshal_load + def self._load(str) + if str == "" + b = Bitmap.new(1,1) + b.dispose + return b + end + w, h, zdata = str.unpack("LLa*"); b = new(w, h) + TKTK_Bitmap.set_pixel_data(b, Zlib::Inflate.inflate(zdata)) + return b + end + + def address + TKTK_Bitmap.address(self) + end + + # ぼかし効果 + def blur2(r=1) + TKTK_Bitmap.blur(self, r) + end + + # 色調変更 + def change_tone(red, green, blue, simplify = 1) + TKTK_Bitmap.change_tone(self, red, green, blue, simplify) + end + + # クリッピング + def clip_mask(bitmap, x=0, y=0, outer=0) + TKTK_Bitmap.clip_mask(self, bitmap, x, y, outer) + end + + # 色の反転 + def invert + TKTK_Bitmap.invert(self) + end + + # モザイク効果 + def mosaic(msw=5, msh=5) + TKTK_Bitmap.mosaic(self, msw, msh) + end + + # モザイク効果(領域指定) + def mosaic_rect(rect=self.rect, msw=5, msh=5) + TKTK_Bitmap.mosaic_rect(self, rect, msw, msh) + end + + # ブレンディング + def blend_blt(x, y, src_bmp, rect, blend_type=0, opacity=255) + return if opacity <= 0 + TKTK_Bitmap.blend_blt(self, x, y, src_bmp, rect, blend_type, opacity) + end + + #png形式で保存 + def png_save(outp, level = 9, filter = PNG_NO_FILTERS) + if (TKTK_Bitmap.png_save(self, outp, level, filter) != 0) + raise("Bitmap\#png_save failed") + end + end + + # 大きいサイズのBitmap.newを可能に(危険) + # width * height が大体1073741823位まで + if TKTK_Bitmap::LARGE_BITMAP + class << self + unless method_defined?(:_hn_large_bm__new) + alias :_hn_large_bm__new :new + end + def new(*args) + if args.size == 2 && args[0] * args[1] >= 4194304 + new_width = args[0] + new_height = args[1] + # とりあえず小さなサイズで作成 + bitmap = _hn_large_bm__new(16, 16) + TKTK_Bitmap.change_size(bitmap, new_width, new_height) + return bitmap + else + _hn_large_bm__new(*args) + end + end + end # Bitmap.new + end +end # CLASS Bitmap diff --git a/Kawariki-patches/ports/debug/basewt.rb b/Kawariki-patches/ports/debug/basewt.rb new file mode 100644 index 0000000..293e236 --- /dev/null +++ b/Kawariki-patches/ports/debug/basewt.rb @@ -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 diff --git a/Kawariki-patches/ports/debug/savebitmanwin32api.rb b/Kawariki-patches/ports/debug/savebitmanwin32api.rb new file mode 100644 index 0000000..63cfd83 --- /dev/null +++ b/Kawariki-patches/ports/debug/savebitmanwin32api.rb @@ -0,0 +1,2723 @@ +#============================================================================== +# ■ Audio +#============================================================================== +module Audio + class << self + #-------------------------------------------------------------------------- + # ● 基本初期化処理 + #-------------------------------------------------------------------------- + def init_basic + bgm_stop + bgs_stop + @fiber_bgm = nil + @fiber_bgs = nil + end + + #-------------------------------------------------------------------------- + # ● 更新処理 + #-------------------------------------------------------------------------- + def update; end + + def alias_file_name(file_name) + NWConst::Audio::AUDIO_ALIAS[file_name] || file_name + end + + alias hima_alias_audio_bgm_play bgm_play + def bgm_play(*args) + args[0] = alias_file_name(args[0]) + hima_alias_audio_bgm_play(*args) + end + + alias hima_alias_audio_me_play me_play + def me_play(*args) + args[0] = alias_file_name(args[0]) + hima_alias_audio_me_play(*args) + end + + alias hima_alias_audio_se_play se_play + def se_play(*args) + args[0] = alias_file_name(args[0]) + hima_alias_audio_se_play(*args) + end + + alias hima_alias_audio_bgs_play bgs_play + def bgs_play(*args) + args[0] = alias_file_name(args[0]) + hima_alias_audio_bgs_play(*args) + end + end + end + + module BattleManager + class << self + #-------------------------------------------------------------------------- + # ○ セットアップ + #-------------------------------------------------------------------------- + def setup(troop_id, can_escape = true, can_lose = false) + init_members + $game_troop.setup(troop_id) + @can_escape = can_escape + @can_lose = can_lose + @troop_id = troop_id + make_escape_ratio + setup_terrain + $game_temp.battle_init + end + + def init_members + @phase = :init # 戦闘進行フェーズ + @can_escape = false # 逃走可能フラグ + @can_lose = false # 敗北可能フラグ + @event_proc = nil # イベント用コールバック + @preemptive = false # 先制攻撃フラグ + @surprise = false # 不意打ちフラグ + @actor_index = -1 # コマンド入力中のアクター + @action_forced = nil # 戦闘行動の強制 + @map_bgm = nil # 戦闘前の BGM 記憶用 + @map_bgs = nil # 戦闘前の BGS 記憶用 + @action_battlers = [] # 行動順序リスト + @action_game_masters = [] # 行動順序リスト(GM専用) + @giveup = false # 降参フラグ + @giveup_count = 0 # 降参カウント + @bind_count = 0 # 拘束カウント + @terrain = :未定義 # 地形 + @troop_id = nil + @fastest_action = nil + @mimic_action_history = [] + @retry_data = nil + end + + #-------------------------------------------------------------------------- + # ○ エンカウント時の処理 + #-------------------------------------------------------------------------- + def on_encounter + @preemptive = (rand < rate_preemptive) + @surprise = (rand < rate_surprise && !@preemptive) + print "先制攻撃発動率#{(rate_preemptive * 100.0).to_i}%.先制#{@preemptive ? '成功' : '失敗'}。\n" + print "不意打ち発動率#{(rate_surprise * 100.0).to_i}%.不意打ち#{@surprise ? '成功' : '失敗'}。\n" + end + + #-------------------------------------------------------------------------- + # ○ 戦闘 BGM の演奏 + #-------------------------------------------------------------------------- + def play_battle_bgm + $game_troop.battle_bgm.play unless bgm_same?($game_troop.battle_bgm) + RPG::BGS.stop + end + + #-------------------------------------------------------------------------- + # ● 初期フェイズ + #-------------------------------------------------------------------------- + def init_phase + @phase = :init + end + + #-------------------------------------------------------------------------- + # ● 仲間入れ替えフェイズ + #-------------------------------------------------------------------------- + def shift_change + @phase = :shift_change + $game_party.clear_actions + end + + #-------------------------------------------------------------------------- + # ● 仲間入れ替えフェイズ? + #-------------------------------------------------------------------------- + def shift_change? + @phase == :shift_change + end + + def battle_end? + @phase.nil? + end + + #-------------------------------------------------------------------------- + # ● 降参 + #-------------------------------------------------------------------------- + def giveup + @giveup = true + @giveup_count = 10 + $game_party.clear_actions + $game_party.all_members.reject { |m| m.luca? }.each { |m| m.hide } + ($game_party.all_members + $game_troop.alive_members).each { |m| m.clear_states } + luca_index = 0 + $game_party.all_members.each_with_index do |actor, i| + luca_index = (actor.luca? ? i : luca_index) + end + $game_party.swap_order(0, luca_index) + end + + #-------------------------------------------------------------------------- + # ● 降参中? + #-------------------------------------------------------------------------- + def giveup? + @giveup + end + + #-------------------------------------------------------------------------- + # ● 降参カウントダウン + #-------------------------------------------------------------------------- + def giveup_count_down + @giveup_count -= 1 + @giveup_count == 0 + end + + #-------------------------------------------------------------------------- + # ● 拘束セット + #-------------------------------------------------------------------------- + def bind_set(count) + bind_reset + @bind_count = count + @bind_start_turn = $game_troop.turn_count + end + + #-------------------------------------------------------------------------- + # ● 拘束ターン + #-------------------------------------------------------------------------- + def binding_turn + $game_troop.turn_count - @bind_start_turn + end + + #-------------------------------------------------------------------------- + # ● 拘束カウントダウン + #-------------------------------------------------------------------------- + def bind_count_down + @bind_count -= 1 + bind_refresh + end + + #-------------------------------------------------------------------------- + # ● 拘束リフレッシュ + #-------------------------------------------------------------------------- + def bind_refresh + bind_reset unless bind? + end + + #-------------------------------------------------------------------------- + # ● 拘束発生中? + #-------------------------------------------------------------------------- + def bind? + 0 != @bind_count && bind_user_exist? && bind_target_exist? + end + + #-------------------------------------------------------------------------- + # ● 拘束状態のリセット + #-------------------------------------------------------------------------- + def bind_reset + $game_party.members.each { |m| m.clear_actions if m.bind_target? } + $game_troop.members.each { |m| m.clear_actions if m.bind_user? } + @bind_count = 0 + $game_troop.members.each do |m| + m.remove_state(NWConst::State::UBIND) + m.remove_state(NWConst::State::EUBIND) + end + $game_party.members.each do |m| + m.remove_state(NWConst::State::TBIND) + m.remove_state(NWConst::State::ETBIND) + end + end + + #-------------------------------------------------------------------------- + # ● 拘束技使用者は存在する? + #-------------------------------------------------------------------------- + def bind_user_exist? + $game_troop.members.any? { |m| m.bind_user? } + end + + #-------------------------------------------------------------------------- + # ● 拘束技対象は存在する? + #-------------------------------------------------------------------------- + def bind_target_exist? + $game_party.members.any? { |m| m.bind_target? } + end + + #-------------------------------------------------------------------------- + # ● 拘束技使用者のインデックス + #-------------------------------------------------------------------------- + def bind_user_index + $game_troop.members.each_with_index do |member, i| + return i if member.bind_user? + end + -1 + end + + #-------------------------------------------------------------------------- + # ○ 逃走成功率の作成 + #-------------------------------------------------------------------------- + def make_escape_ratio + lv = 0 + lv = if $game_party.in_member_luca? + $game_actors[NWConst::Actor::LUCA].base_level + else + $game_party.all_members.map(&:base_level).max + end + escape_level = $game_troop.escape_level_max + @escape_ratio = lv >= escape_level ? 1.0 : (1.0 / (escape_level - lv + 1)) + end + + #-------------------------------------------------------------------------- + # ● 戦闘地形の設定 + #-------------------------------------------------------------------------- + def setup_terrain + NWConst::Field::TERRAIN.each do |key, value| + next if value[:tag] != $game_player.terrain_tag && !value[:map_id].include?($game_map.map_id) + + @terrain = key + break + end + end + + #-------------------------------------------------------------------------- + # ● 戦闘地形の取得 + #-------------------------------------------------------------------------- + attr_reader :terrain + + #-------------------------------------------------------------------------- + # ○ 逃走許可の取得 + #-------------------------------------------------------------------------- + def can_escape? + @can_escape && !bind? && !$game_troop.challenge_battle? + end + + #-------------------------------------------------------------------------- + # ○ 戦闘開始 + #-------------------------------------------------------------------------- + def battle_start + @retry_data = Marshal.dump(DataManager.make_save_contents) + $game_variables[NWConst::Var::BATTLE_END_TURN] = 0 + $game_temp.reserve_common_event(NWConst::Common::BATTLE_START) + $game_party.on_battle_start + $game_troop.on_battle_start + $game_troop.enemy_names.each do |name| + $game_message.add(format(Vocab::Emerge, name)) + end + tmp = [] + $game_troop.members.each { |enemy| tmp.push(enemy.id) if enemy } + $game_library.enemy.set_discovery(tmp) + if @preemptive + $game_message.add(format(Vocab::Preemptive, $game_party.name)) + elsif @surprise + $game_message.add(format(Vocab::Surprise, $game_party.name)) + end + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● 好感度上昇 + #-------------------------------------------------------------------------- + def gain_love + $game_party.battle_members.select do |member| + !member.luca? + end.each do |member| + member.love += $game_variables[NWConst::Var::BATTLE_END_GAIN_LOVE] + end + end + + #-------------------------------------------------------------------------- + # ○ 逃走の処理 + #-------------------------------------------------------------------------- + def process_escape + $game_message.add(format(Vocab::EscapeStart, $game_party.name)) + success = @preemptive ? true : (rand < @escape_ratio) + Sound.play_escape + if success + process_abort + else + $game_message.add('\.' + Vocab::EscapeFailure) + $game_party.clear_actions + end + wait_for_message + success + end + + #-------------------------------------------------------------------------- + # ● 強制逃走の処理 + #-------------------------------------------------------------------------- + def process_forced_escape + $game_message.add(format(Vocab::EscapeStart, $game_party.name)) + Sound.play_escape + if can_forced_escape? + process_abort + else + $game_message.add('\.' + Vocab::EscapeFailure) + end + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● 強制逃走が可能か + #-------------------------------------------------------------------------- + def can_forced_escape? + can_escape? and !($game_switches[NWConst::Sw::STRICT_ENCOUNT]) + end + + #-------------------------------------------------------------------------- + # ● プレイヤのリセット + #-------------------------------------------------------------------------- + def reset_player + $game_player.transparent = true + $game_player.followers.visible = false + $game_player.moveto(0, 0) + $game_player.refresh + end + + #-------------------------------------------------------------------------- + # ● スキップ不能か + #-------------------------------------------------------------------------- + def no_lose_skip? + enemy_id = $game_troop.lose_event_id - NWConst::Common::LOSE_EVENT_BASE + return true if enemy_id == 0 # LOSE_EVENT_BASEを直接実行=混沌の迷宮エネミー + return true if $data_enemies[enemy_id].no_lose_skip? + + false + end + + #-------------------------------------------------------------------------- + # ○ 戦闘終了 + # result : 結果(0:勝利 1:逃走 2:敗北) + #-------------------------------------------------------------------------- + def battle_end(result) + @phase = nil + @giveup = false + @event_proc.call(result) if @event_proc + $game_temp.reserve_common_event(NWConst::Common::BATTLE_END) + $game_party.on_battle_end + $game_troop.on_battle_end + SceneManager.exit if $BTEST + $game_temp.battle_init + @retry_data = nil unless result == 2 + end + + #-------------------------------------------------------------------------- + # ○ 獲得した経験値の表示 + #-------------------------------------------------------------------------- + def display_exp + if $game_troop.exp_total > 0 + text = format(Vocab::ObtainExp, $game_troop.exp_total) + $game_message.add('\.' + text) + end + if $game_troop.class_exp_total > 0 + text2 = format(Vocab::ObtainJobExp, $game_troop.class_exp_total) + $game_message.add('\.' + text2) + end + end + + #-------------------------------------------------------------------------- + # ○ 経験値の獲得とレベルアップの表示 + #-------------------------------------------------------------------------- + def gain_exp + $game_party.all_members.each do |actor| + actor.gain_exp($game_troop.exp_total, $game_troop.class_exp_total) + end + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● ドロップアイテムの獲得と表示 【再定義】 + #-------------------------------------------------------------------------- + def gain_drop_items + $game_temp.clear_get_item + + $game_troop.make_drop_items.each do |item| + $game_party.gain_item(item, 1) + end + + $game_temp.get_item_data.each do |item, value| + value.times do + $game_message.add(Vocab.item_get_message(item, 1)) + end + end + + wait_for_message + end + + def turn_start + @phase = :turn + @mimic_action_history = [] + clear_actor + $game_party.set_action_history + $game_party.check_change_action + $game_troop.increase_turn + make_action_orders + end + + def setup_auto_skill? + !giveup? || $game_party.in_battle || !$game_troop.interpreter.running? + end + + def set_battle_start_skill + set_auto_skill(&:battle_start_skill) + end + + def set_turn_start_skill + set_auto_skill(&:turn_start_skill) + end + + def set_turn_end_skill + set_auto_skill(&:turn_end_skill) + set_turn_end_revive + end + + def counter_skill(battler) + auto_skill_per(battler.counter_skill, battler) + end + + def set_auto_skill + @action_game_masters = [] + return unless setup_auto_skill? + + members = ($game_troop.alive_members + $game_party.alive_members).select(&:enable_action?).reject(&:cant_move?) + members.each do |member| + skills = yield member + auto_skills_per(skills, member).reverse_each do |skill| + act = skill_interrupt(member, skill) + act.target_index = member.index if act && act.object && act.object.scope == 7 + end + end + end + + def auto_skills_per(skills, battler) + r = _auto_skill_per(skills, battler).group_by do |skill| + skill.fetch(:priority, 99) + end + r.sort_by { |k, _| k }.map do |_, ss| + ss.sample.fetch(:id) + end + end + + def auto_skill_per(skills, battler) + _auto_skill_per(skills, battler).map { |s| s.fetch(:id) }.sample + end + + def _auto_skill_per(skills, battler) + skills.select do |obj| + if obj[:condition_type] + next false unless battler.skill_race_ok?($data_skills[obj[:id]]) + + result = case obj[:condition_type] + when 1 + members = $game_party.battle_members_id + obj[:condition_ids].any? { |id| members.include?($game_actors.original_id(id)) } + when 2 + members = $game_troop.members.map(&:id) + obj[:condition_ids].any? { |id| members.include?(id) } + when 3 + members = $game_party.battle_members + obj[:condition_ids].any? { |id| members.any? { |m| m.state?(id) } } + when 4 + members = $game_troop.members + obj[:condition_ids].any? { |id| members.any? { |m| m.state?(id) } } + when 5 + obj[:condition_ids].any? { |id| battler.state?(id) } + else + true + end + next false unless result + end + rand < obj[:per] + end + end + + def set_dead_skill(battler) + return unless setup_auto_skill? + + battler.dead_skill.each do |skill| + skill_interrupt(battler, skill, :dead_skill) + end + end + + def set_final_invoke(battler) + return unless setup_auto_skill? + + battler.final_invoke.each do |skill| + skill_interrupt(battler, skill, :final_invoke) + end + end + + #-------------------------------------------------------------------------- + # ○ 戦闘回想かどうか + #-------------------------------------------------------------------------- + def memory_battle? + $game_temp.in_memory_battle + end + + #-------------------------------------------------------------------------- + # ○ 経験値を入手可能か + #-------------------------------------------------------------------------- + def enable_get_exp? + return false if memory_battle? + return false if $game_switches[NWConst::Sw::GET_DISABLE_EXP_DROP] + return false if $game_switches[NWConst::Sw::GET_DISABLE_EXP] + + true + end + + #-------------------------------------------------------------------------- + # ○ ゴールドを入手可能か + #-------------------------------------------------------------------------- + def enable_get_gold? + return false if memory_battle? + return false if $game_switches[NWConst::Sw::GET_DISABLE_GOLD] + + true + end + + #-------------------------------------------------------------------------- + # ○ ドロップアイテムを入手可能か + #-------------------------------------------------------------------------- + def enable_get_drop? + return false if memory_battle? + return false if $game_switches[NWConst::Sw::GET_DISABLE_EXP_DROP] + return false if $game_switches[NWConst::Sw::GET_DISABLE_DROP] + + true + end + + #-------------------------------------------------------------------------- + # ○ 敗北カウントを行うか + #-------------------------------------------------------------------------- + def enable_party_lose_count? + $game_troop.members.each do |enemy| + return false if enemy.enemy.no_lose_skip? + end + true + end + + def play_battle_end_me + $game_system.battle_end_me.play unless bgm_same?($game_troop.battle_bgm) + end + + #-------------------------------------------------------------------------- + # ○ 勝利の処理 + #-------------------------------------------------------------------------- + def process_victory + @phase = nil + $game_variables[NWConst::Var::BATTLE_END_TURN] = $game_troop.turn_count + play_battle_end_me + replay_bgm_and_bgs + $game_temp.in_victory_message = true + $game_message.add(format(Vocab::Victory, $game_party.name)) + display_exp if enable_get_exp? + gain_gold if enable_get_gold? + gain_drop_items if enable_get_drop? + gain_exp if enable_get_exp? + gain_love + process_follow unless memory_battle? + wait_for_message if memory_battle? + $game_temp.in_victory_message = false + SceneManager.return + battle_end(0) + DataManager.auto_save_game if !memory_battle? && !@event_proc + true + end + + #-------------------------------------------------------------------------- + # ○ 敗北の処理 ベース/Module + #-------------------------------------------------------------------------- + def process_defeat + @phase = nil + SceneManager.scene.process_common_event_on_defeat if $game_temp.common_event_reserved? + $game_message.add(format(Vocab::Defeat, $game_party.name)) + wait_for_message + if @can_lose + revive_battle_members + replay_bgm_and_bgs + SceneManager.return + else + # 通常のゲームオーバーは完全排除 + Audio.bgm_stop + Audio.bgs_stop + revive_battle_members + unless memory_battle? + $game_map.interpreter.clear + reset_player + end + change_novel_scene + end + battle_end(2) + true + end + + #-------------------------------------------------------------------------- + # ● ノベルパートへの移行 + #-------------------------------------------------------------------------- + def change_novel_scene + unless memory_battle? + SceneManager.clear + SceneManager.push(Scene_Map) + end + $game_novel.setup($game_troop.lose_event_id) + SceneManager.goto(Scene_Novel) + + skip_flag = $game_system.conf[:ls_skip] == 1 + skip_flag &&= $game_library.lose_event_view?($game_novel.event_id) + check_flag = $game_system.conf[:ls_skip] == 2 + choice = -1 + if no_lose_skip? + skip_flag = false + check_flag = false + end + if check_flag + $game_message.add("Skip defeat scene?") + ["Yes", "No"].each { |s| $game_message.choices.push(s) } + $game_message.choice_cancel_type = 2 + $game_message.choice_proc = proc { |n| choice = n } + wait_for_message + end + if no_lose_skip? and memory_battle? + $game_novel.interpreter.memory_interruption + elsif skip_flag || (choice == 0) + $game_novel.interpreter.goto_ilias + end + end + + def follower_disable? + $game_switches[NWConst::Sw::FOLLOWER_DISABLE_1] || $game_switches[NWConst::Sw::FOLLOWER_DISABLE_2] || ($game_party.temp_actors_use? && !$game_party.multi_party?) + end + + #-------------------------------------------------------------------------- + # ● 仲間化の処理 + #-------------------------------------------------------------------------- + def process_follow + return if follower_disable? + + $game_troop.check_getup + return unless $game_troop.follower_enemy + + if NWConst::Follow::SPECIAL.include?($game_troop.follower_enemy.id) + send("process_follow_enemy#{$game_troop.follower_enemy.id}".to_sym) + else + process_follow_normal + end + end + + #-------------------------------------------------------------------------- + # ● 仲間加入時の質問処理 + #-------------------------------------------------------------------------- + def process_follow_question + e = $game_troop.follower_enemy + e.follow_question_word.execute + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● 仲間加入時の選択肢処理 + #-------------------------------------------------------------------------- + def process_follow_choice(follower_name = nil) + e = $game_troop.follower_enemy + follower_name ||= e.original_name + $game_message.add("#{follower_name} manages to rise back up.") + $game_message.add("She looks like she wants to join your party!\f") + $game_message.add("Add her as a companion?") + choice = 0 + ["Yes", "No"].each { |s| $game_message.choices.push(s) } + $game_message.choice_cancel_type = 2 + $game_message.choice_proc = proc { |n| choice = n } + wait_for_message + + choice == 0 + end + + #-------------------------------------------------------------------------- + # ● 仲間加入時の承諾処理 + #-------------------------------------------------------------------------- + def process_follow_ok(follower_name = nil) + e = $game_troop.follower_enemy + follower_name ||= e.original_name + e.follow_yes_word.execute + wait_for_message + $game_message.add("#{follower_name} has joined the party!") + if $game_party.multi_party? + $game_message.add("#{wait_member_name} heads back to the pocket castle!") + $game_party.add_stand_actor(e.follower_actor_id) + $game_temp.getup_enemy = e.follower_actor_id + wait_for_message + return + end + wait_for_message + process_follow_ok_member_full(e.follower_actor_id, follower_name) if $game_party.party_member_full? + # 仲間になったエネミーを保存 + $game_actors[e.follower_actor_id].setup(e.follower_actor_id) + $game_party.add_actor(e.follower_actor_id) + $game_temp.getup_enemy = e.follower_actor_id + end + + #-------------------------------------------------------------------------- + # ● パーティ満員時の選択 + #-------------------------------------------------------------------------- + def process_follow_ok_member_full(follower_id, follower_name = nil) + $game_message.add("The party is full.") + $game_message.add("Please choose a member to remove from the party.") + wait_for_message + stand_actor = $game_party.choice_stand_actor_on_member_full(follower_id, follower_name) + $game_party.move_stand_actor(stand_actor.id) if stand_actor + wait_member_name = stand_actor ? stand_actor.name : follower_name + $game_message.add("#{wait_member_name} heads back to the pocket castle!") + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● 仲間加入時の拒否処理 + #-------------------------------------------------------------------------- + def process_follow_no + e = $game_troop.follower_enemy + e.follow_no_word.execute + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● 仲間加入時の去る処理 + #-------------------------------------------------------------------------- + def process_follow_bye(follower_name = nil) + e = $game_troop.follower_enemy + follower_name ||= e.original_name + $game_troop.follower_enemy = nil + $game_message.add("#{follower_name} sulks away...") + wait_for_message + end + + #-------------------------------------------------------------------------- + # ● 仲間加入時演出(通常パターン) + #-------------------------------------------------------------------------- + def process_follow_normal + process_follow_question + if process_follow_choice + process_follow_ok + else + process_follow_no + process_follow_bye + end + end + + #-------------------------------------------------------------------------- + # ● ピクチャーの簡易操作 + #-------------------------------------------------------------------------- + def pic_easy_setup(num, name, x, y) + name = "../Battlers/#{name}" + $game_troop.screen.pictures[num].easy_setup(name, x, y) + end + + def pic_easy_appear_move(num, x, y) + $game_troop.screen.pictures[num].easy_appear_move(x, y) + end + + def pic_easy_appear(num) + $game_troop.screen.pictures[num].easy_appear + end + + def pic_easy_erase(num) + $game_troop.screen.pictures[num].easy_erase + end + + #-------------------------------------------------------------------------- + # ● ピクチャーの表示 + #-------------------------------------------------------------------------- + def pic_show(num, name, ori, x, y, zx, zy, op, bl) + name = "../Battlers/#{name}" + $game_troop.screen.pictures[num].show(name, ori, x, y, zx, zy, op, bl) + end + + #-------------------------------------------------------------------------- + # ● ピクチャーの移動 + #-------------------------------------------------------------------------- + def pic_move(num, ori, x, y, zx, zy, op, bl, dur) + $game_troop.screen.pictures[num].move(ori, x, y, zx, zy, op, bl, dur) + end + + #-------------------------------------------------------------------------- + # ● ピクチャーのクリア + #-------------------------------------------------------------------------- + def pic_clear + $game_troop.screen.clear_pictures + end + + def skill_combo(battler, action, base_action) + action.set_symbol(:skill_combo) + return unless base_action.skill_combo_enable?(action) + + action.combo_setting(base_action) + battler.actions << action + @action_battlers.unshift(action) + end + + #-------------------------------------------------------------------------- + # ○ 次の行動主体の取得 + # 行動順序リストの先頭からバトラーを取得する。 + # 現在パーティにいないアクターを取得した場合(index が nil, バトルイベ + # ントでの離脱直後などに発生)は、それをスキップする。 + #-------------------------------------------------------------------------- + def next_subject + if gm_exist? + subject = @action_game_masters.shift + return battler(subject) + end + + loop do + subject = @action_battlers.shift + return nil unless subject + + battler = battler(subject) + next unless battler.index + + return battler + end + end + + #-------------------------------------------------------------------------- + # ○ 勝敗判定 + #-------------------------------------------------------------------------- + def judge_win_loss + if @phase && !gm_exist? + return process_abort if $game_party.members.empty? + return process_defeat if $game_party.all_dead? + return process_victory if $game_troop.all_dead? + return process_abort if aborting? + end + false + end + + #-------------------------------------------------------------------------- + # ● GMアクションが存在する? + #-------------------------------------------------------------------------- + def gm_exist? + !@action_game_masters.empty? + end + + #-------------------------------------------------------------------------- + # ○ BGM と BGS の再開 + #-------------------------------------------------------------------------- + def replay_bgm_and_bgs + @map_bgm.replay unless $BTEST || bgm_same?(RPG::BGM.last) + @map_bgs.replay unless $BTEST + $game_map.autoplay_field if $game_map.auto_bgm? + end + + def skill_interrupt(battler, skill_id, symbol = :interrupt) + return unless battler.skill_race_ok?($data_skills[skill_id]) + + battler = Game_Master.new(battler) + action = battler.skill_interrupt(skill_id, symbol) + @action_game_masters.unshift(action) + action + end + + def make_action_orders + ab = [] + ab += $game_troop.members unless @preemptive + ab += $game_party.members unless @surprise + ab.each(&:make_speed) + i = 0 + + @action_battlers = ab.map { |b| b.actions }.flatten.compact + @action_battlers.sort_by! { |v| [v, i += 1] } + @action_battlers += ab.select { |m| m.actions.empty? }.shuffle + @fastest_action = @action_battlers.first + end + + def delete_action(subject) + @action_battlers.reject! { |ab| battler(ab) == subject } + end + + def battler(obj) + return nil unless obj + + if obj.action? + obj.subject.current_action_index = obj + obj.subject + else + obj.current_action_index = nil + obj + end + end + + def force_action(battler) + @action_forced = battler + delete_action(battler) + end + + def bgm_same?(bgm) + return false unless @map_bgm + + @map_bgm.name == bgm.name && @map_bgm.volume == bgm.volume && @map_bgm.pitch == bgm.pitch + end + + def can_giveup? + return false if $game_switches[NWConst::Sw::INVALID_GIVEUP] + return false if $game_troop.challenge_battle? + + $game_party.all_members.any? { |member| member.luca? } && $game_actors[NWConst::Actor::LUCA].exist? + end + + def set_turn_end_revive + return if giveup? || !$game_party.in_battle || $game_troop.interpreter.running? + + members = ($game_troop.dead_members + $game_party.dead_members).select(&:turn_end_revive?) + members.each do |member| + skill_interrupt(member, TURN_END_REVIVE_SKILL.id) + end + end + + def skill_chain(battler, base_action) + action = base_action.chain_action + return if action.nil? + + battler.actions << action + @action_battlers.unshift(action) + end + + def add_mimic_history(action) + return if action.nil? || action.symbol != :count + + @mimic_action_history << action + end + + def interrupt_mimic(batller) + mimic_skills(battler).reverse_each do |skill_id| + skill_interrupt(batller, skill_id, :mimic) + end + end + + def interrupt_super_mimic(battler) + super_mimic_skills(battler).reverse_each do |skill_id| + skill_interrupt(battler, skill_id, :mimic) + end + end + + def mimic_skills(action) + m = mimic_items.last + return [] if m.nil? || !action.subject.mimic_usable?(m) + + [m.id] + end + + def super_mimic_skills(battler) + items = mimic_items.select do |a| + battler.mimic_usable?(a) + end + items.map(&:id) + end + + def mimic_items + @mimic_action_history.map(&:object).compact + end + + def evasion_skill(battler) + auto_skill_per(battler.evasion_skill, battler) + end + + def interrupt_evasion_skill + members = ($game_troop.alive_members + $game_party.alive_members).select(&:enable_action?).reject(&:cant_move?) + members.select(&:evasion_action).each do |member| + act = member.evasion_action + bact = skill_interrupt(member, act.object.id) + bact.target_index = act.target_index if bact + member.clear_evasion_action + end + end + + def fastest_action?(action) + return false unless $game_party.in_battle + + @fastest_action == action + end + + def latest? + return false unless $game_party.in_battle + + @action_battlers.select(&:action?).select { |a| a.symbol == :count }.empty? + end + + def retry_battle + return if $game_party.in_battle || @retry_data.nil? + + SceneManager.push(Scene_Battle) + bgm = @map_bgm + bgs = @map_bgs + DataManager.extract_save_contents(Marshal.load(@retry_data)) + setup(@troop_id, @can_escape, @can_lose) + play_battle_bgm + @map_bgm = bgm + @map_bgs = bgs + true + end +end +end + +module Cache + class << self + alias h_normal_bitmap normal_bitmap + def normal_bitmap(path) + check_filesize + h_normal_bitmap(path) + end + + alias h_hue_changed_bitmap hue_changed_bitmap + def hue_changed_bitmap(path, hue) + check_filesize + h_hue_changed_bitmap(path, hue) + end + + def check_filesize + clear if @cache.size > 600 + end + end +end + +#============================================================================== +# ■ DataManager +#============================================================================== +module DataManager + class << self + #-------------------------------------------------------------------------- + # ○ データベースのロード + #-------------------------------------------------------------------------- + def load_database + if $BTEST + load_battle_test_database + else + load_normal_database + check_player_location + end + $data_library = Data_Library.new + end + + #-------------------------------------------------------------------------- + # ○ 各種ゲームオブジェクトの作成 + #-------------------------------------------------------------------------- + def create_game_objects + $game_temp = Game_Temp.new + $game_system = Game_System.new + $game_timer = Game_Timer.new + $game_message = Game_Message.new + $game_switches = Game_Switches.new + $game_variables = Game_Variables.new + $game_self_switches = Game_SelfSwitches.new + $game_actors = Game_Actors.new + $game_party = Game_Party.new + $game_troop = Game_Troop.new + $game_map = Game_Map.new + $game_player = Game_Player.new + $game_novel = Game_Novel.new + $game_poker = Game_Poker.new + $game_slot = Game_Slot.new + end + + #-------------------------------------------------------------------------- + # ○ 戦闘テストのセットアップ + #-------------------------------------------------------------------------- + def setup_battle_test + $game_map.setup($data_system.start_map_id) + $game_party.setup_battle_test + BattleManager.setup($data_system.test_troop_id) + BattleManager.play_battle_bgm + end + + #-------------------------------------------------------------------------- + # ○ セーブ内容の作成 + #-------------------------------------------------------------------------- + def make_save_contents + contents = {} + contents[:system] = $game_system + contents[:timer] = $game_timer + contents[:message] = $game_message + contents[:switches] = $game_switches + contents[:variables] = $game_variables + contents[:self_switches] = $game_self_switches + contents[:actors] = $game_actors + contents[:party] = $game_party + contents[:troop] = $game_troop + contents[:map] = $game_map + contents[:player] = $game_player + contents[:novel] = $game_novel + contents + end + + #-------------------------------------------------------------------------- + # ○ セーブ内容の展開 + #-------------------------------------------------------------------------- + def extract_save_contents(contents) + $game_system = contents[:system] + $game_timer = contents[:timer] + $game_message = contents[:message] + $game_switches = contents[:switches] + $game_variables = contents[:variables] + $game_self_switches = contents[:self_switches] + $game_actors = contents[:actors] + $game_party = contents[:party] + $game_troop = contents[:troop] + $game_map = contents[:map] + $game_player = contents[:player] + $game_novel = contents[:novel] + end + + #-------------------------------------------------------------------------- + # ● ロードの実行(例外処理なし) + #-------------------------------------------------------------------------- + def load_game_without_rescue(index) + File.open(make_filename(index), "rb") do |file| + Marshal.load(file) + extract_save_contents(Marshal.load(file)) + update + reload_map_if_updated + @last_savefile_index = index.is_a?(Integer) ? index : 0 + end + true + end + + #-------------------------------------------------------------------------- + # ○ モジュール初期化 + #-------------------------------------------------------------------------- + def init + @last_savefile_index = 0 + create_game_objects + setup_battle_test if $BTEST + end + + #-------------------------------------------------------------------------- + # ● システムバックアップファイル名の取得 + #-------------------------------------------------------------------------- + def system_backup_filename + "Save/SystemSaveBackup.rvdata2" + end + + #-------------------------------------------------------------------------- + # ● システムデータの初期化 + #-------------------------------------------------------------------------- + def setup_system + return if system_object_exist? + + if system_file_exist? + load_system + fix_system + else + create_system_objects + save_system + end + $game_library.make_preparation_list + end + + def fix_system + $game_global_system.fix + end + + #-------------------------------------------------------------------------- + # ● システムオブジェクトが存在する? + #-------------------------------------------------------------------------- + def system_object_exist? + ($game_library && $game_system_switches) + end + + #-------------------------------------------------------------------------- + # ● システムバックアップセーブの実行(例外処理なし) + #-------------------------------------------------------------------------- + def save_system_backup_without_rescue + File.open(system_backup_filename, "wb") do |file| + Marshal.dump(make_system_save_contents, file) + end + true + end + + #-------------------------------------------------------------------------- + # ● システムセーブ内容の作成 + #-------------------------------------------------------------------------- + def make_system_save_contents + contents = {} + contents[:system_save_count] = @system_save_count + contents[:library] = $game_library + contents[:system_switches] = $game_system_switches + contents[:global_system] = $game_global_system + contents + end + + #-------------------------------------------------------------------------- + # ● システムセーブ内容の展開 + #-------------------------------------------------------------------------- + def extract_system_save_contents(contents) + @system_save_count = contents[:system_save_count].nil? ? 0 : contents[:system_save_count] + $game_library = contents[:library].nil? ? Game_Library.new : contents[:library] + $game_system_switches = contents[:system_switches].nil? ? Game_SystemSwitches.new : contents[:system_switches] + $game_global_system = contents[:global_system] + if $game_global_system.nil? + $game_global_system = Global::Game_System.new + save_system + end + end + + #-------------------------------------------------------------------------- + # ● 各種システムオブジェクトの作成 + #-------------------------------------------------------------------------- + def create_system_objects + p "システムオブジェクトの生成" if $TEST + @system_save_count = 0 + $game_library = Game_Library.new + $game_system_switches = Game_SystemSwitches.new + $game_global_system = Global::Game_System.new + end + + #-------------------------------------------------------------------------- + # ○ セーブファイルの存在判定 + #-------------------------------------------------------------------------- + def save_file_exists? + !Dir.glob("Save/Save*.rvdata2").empty? + end + + #-------------------------------------------------------------------------- + # ○ セーブファイルの最大数 + #-------------------------------------------------------------------------- + def savefile_max + 99 + end + + #-------------------------------------------------------------------------- + # ● ファイル名の作成 + # index : ファイルインデックス + #-------------------------------------------------------------------------- + def make_filename(index) + if index.is_a?(Integer) + format("Save/Save%02d.rvdata2", index + 1) + else + format("Save/AutoSave%s.rvdata2", index) + end + end + + #-------------------------------------------------------------------------- + # ○ セーブヘッダの作成 + #-------------------------------------------------------------------------- + def make_save_header + header = {} + header[:characters] = $game_party.characters_for_savefile + header[:playtime_s] = $game_system.playtime_s + header[:realtime_s] = $game_system.realtime_s + header[:luca_level] = $game_actors[NWConst::Actor::LUCA].base_level + header + end + + #-------------------------------------------------------------------------- + # ● サムネイル画像の解放 + #-------------------------------------------------------------------------- + def dispose_thumbnail + @current_thumbnail.dispose + @dummy_thumbnail.dispose + @thumbnails.each_value { |thumbnail| thumbnail.dispose } + @current_thumbnail = nil + @dummy_thumbnail = nil + @thumbnails = {} + end + + #-------------------------------------------------------------------------- + # ● 現在サムネイル画像の取得 + #-------------------------------------------------------------------------- + def get_current_thumbnail + @current_thumbnail + end + + #-------------------------------------------------------------------------- + # ● ダミーサムネイル画像の取得 + #-------------------------------------------------------------------------- + def get_dummy_thumbnail + @dummy_thumbnail + end + + #-------------------------------------------------------------------------- + # ● サムネイル画像の取得 + #-------------------------------------------------------------------------- + def get_thumbnail(index) + @thumbnails[index] + end + + #-------------------------------------------------------------------------- + # ● サムネイル画像の変更 + #-------------------------------------------------------------------------- + def change_thumbnail(index) + @thumbnails[index].dispose if @thumbnails[index] + bitmap = Bitmap.new(160, 120) + bitmap.blt(0, 0, @current_thumbnail, bitmap.rect) + @thumbnails[index] = bitmap + end + + #-------------------------------------------------------------------------- + # ● サムネイル画像の保存 + #-------------------------------------------------------------------------- + def save_thumbnail(file_name) + @current_thumbnail.save(file_name, :PNG) + end + + def update_database? + script_update? || diff_version_id? || data_update? + end + + def diff_version_id? + load_data("Data/ExVersionID.rvdata2") != load_data("Data/System.rvdata2").version_id + rescue Errno::ENOENT + true + end + + def latest_updatetime + return 0 if rgss_encrypt? + + files = ["Data/Actors", "Data/Classes", "Data/Skills", "Data/Items", "Data/Weapons", "Data/Armors", + "Data/Enemies", "Data/Troops", "Data/States", "Data/Animations", "Data/Tilesets", "Data/CommonEvents", "Data/System", "Data/MapInfos", "#{over_map_dir}/Data/MapInfos"] + times = files.map do |f| + next 0 if File.exist?("#{f}.rvdata2") + + File.mtime("#{f}.rvdata2") + end + times.max + end + + def data_update? + return false if rgss_encrypt? + + load_data("Data/ExDataUpdate.rvdata2") < latest_updatetime + rescue Errno::ENOENT + true + end + + def script_update? + SCRIPT_UPDATE != load_data("Data/ExScriptUpdate.rvdata2") + rescue Errno::ENOENT + true + end + + def rgss_encrypt? + @rgss_encrypt ||= FileTest.file?("Game.rgss3a") + end + + def setting_features_data + $data_actors.compact.each(&:setting_feature_data) + $data_classes.compact.each(&:setting_feature_data) + $data_weapons.compact.each(&:setting_feature_data) + $data_armors.compact.each(&:setting_feature_data) + $data_enemies.compact.each(&:setting_feature_data) + $data_states.compact.each(&:setting_feature_data) + $data_items.compact.each(&:setting_feature_data) + $data_skills.compact.each(&:setting_feature_data) + end + + #-------------------------------------------------------------------------- + # ● 通常のデータベースEXをロード + #-------------------------------------------------------------------------- + def load_normal_database_ex + ex_data = load_data("Data/DataEx.rvdata2") + $data_actors = ex_data[:actors] + $data_classes = ex_data[:classes] + $data_skills = ex_data[:skills] + $data_items = ex_data[:items] + $data_weapons = DataBaseEquip.new(ex_data[:weapons], :weapons) + $data_armors = DataBaseEquip.new(ex_data[:armors], :armors) + $data_enemies = ex_data[:enemies] + $data_troops = ex_data[:troops] + $data_states = ex_data[:states] + $data_animations = ex_data[:animations] + $data_tilesets = ex_data[:tilesets] + $data_common_events = ex_data[:common_events] + $data_system = ex_data[:system] + $data_mapinfos = ex_data[:mapinfos] + end + + #-------------------------------------------------------------------------- + # ● 戦闘テスト用のデータベースEXをロード + #-------------------------------------------------------------------------- + def load_battle_test_database_ex + ex_data = load_data("Data/DataEx.rvdata2") + $data_actors = ex_data[:actors] + $data_classes = ex_data[:classes] + $data_skills = ex_data[:skills] + $data_items = ex_data[:items] + $data_weapons = DataBaseEquip.new(ex_data[:weapons], :weapons) + $data_armors = DataBaseEquip.new(ex_data[:armors], :armors) + $data_enemies = ex_data[:enemies] + $data_troops = load_data("Data/BT_Troops.rvdata2") + $data_states = ex_data[:states] + $data_animations = load_data("Data/BT_Animations.rvdata2") + $data_tilesets = ex_data[:tilesets] + $data_common_events = ex_data[:common_events] + $data_system = load_data("Data/BT_System.rvdata2") + end + + #-------------------------------------------------------------------------- + # ● 解析済みデータベースEXをセーブ + #-------------------------------------------------------------------------- + def save_analyzed_database_ex + ex_data = {} + ex_data[:actors] = $data_actors + ex_data[:classes] = $data_classes + ex_data[:skills] = $data_skills + ex_data[:items] = $data_items + ex_data[:weapons] = $data_weapons + ex_data[:armors] = $data_armors + ex_data[:enemies] = $data_enemies + ex_data[:troops] = $data_troops + ex_data[:states] = $data_states + ex_data[:tilesets] = $data_tilesets + ex_data[:animations] = $data_animations + ex_data[:common_events] = $data_common_events + ex_data[:system] = $data_system + ex_data[:mapinfos] = $data_mapinfos + save_data(ex_data, "Data/DataEx.rvdata2") + save_data($data_system.version_id, "Data/ExVersionID.rvdata2") + save_data(SCRIPT_UPDATE, "Data/ExScriptUpdate.rvdata2") + save_data(latest_updatetime, "Data/ExDataUpdate.rvdata2") + + p "DataEx.rvdata2をセーブしました" + end + + #-------------------------------------------------------------------------- + # ○ 通常のデータベースをロード + #-------------------------------------------------------------------------- + alias nw_base_load_normal_database load_normal_database + def load_normal_database + bm = Benchmark.new + bm.start + if update_database? + nw_base_load_normal_database + nw_analyze_database + setting_features_data + save_analyzed_database_ex unless rgss_encrypt? + else + load_normal_database_ex + end + bm.stop + p "データベース読み込み時間は#{bm.elapsed}です" + $data_skills << TURN_END_REVIVE_SKILL + $data_skills << REVIVE_SKILL + [TURN_END_REVIVE_SKILL, REVIVE_SKILL].each do |skill| + skill.id = $data_skills.index(skill) + skill.note_analyze + skill.setting_feature_data + skill.data_ex[:visible?] = true + skill.data_ex[:lib_exclude?] = true + end + end + + #-------------------------------------------------------------------------- + # ○ 戦闘テスト用のデータベースをロード + #-------------------------------------------------------------------------- + alias nw_base_load_battle_test_database load_battle_test_database + def load_battle_test_database + bm = Benchmark.new + bm.start + if update_database? + nw_base_load_normal_database # Data_Ex作成には通常データベースを使う + nw_analyze_database + setting_features_data + save_analyzed_database_ex + end + load_battle_test_database_ex + bm.stop + p "データベース読み込み時間は#{bm.elapsed}です" if $test_battlers + end + + #-------------------------------------------------------------------------- + # ● セーブファイルの存在判定 + #-------------------------------------------------------------------------- + def auto_save_file_exists? + return false if Dir.glob("Save/AutoSave*.rvdata2").empty? + return false unless load_header("01")[:save_index].is_a?(Integer) + + true + end + + #-------------------------------------------------------------------------- + # ● セーブ試行ファイル名の作成 + #-------------------------------------------------------------------------- + def make_temp_filename + sprintf("Save/SaveTemp.rvdata2") + end + + #-------------------------------------------------------------------------- + # ● セーブ試行ファイルの削除 + #-------------------------------------------------------------------------- + def delete_temp_file + File.delete(make_temp_filename) if File.exist?(make_temp_filename) + end + + #-------------------------------------------------------------------------- + # ○ オートセーブヘッダの作成 + #-------------------------------------------------------------------------- + def make_auto_save_header + header = make_save_header + header[:save_index] = DataManager.last_savefile_index + header + end + + #-------------------------------------------------------------------------- + # ○ セーブの実行 + #-------------------------------------------------------------------------- + def save_game(index) + Dir.mkdir("Save") unless Dir.exist?("Save") + save_game_without_rescue(index) + begin; save_thumbnail(format("Save/Save%02d", index + 1)) + rescue StandardError; process_save_failure2("通常") + end + if $game_system.save_count % SaveData::BackUpNum == 0 + begin + save_game_backup_without_rescue + begin; save_thumbnail("Save/SaveBackup") + rescue StandardError; process_save_failure2("バックアップ") + end + rescue StandardError; process_save_failure1("バックアップ") + end + end + delete_temp_file + true + rescue StandardError + process_save_failure1("通常") + delete_temp_file + false + end + + #-------------------------------------------------------------------------- + # ● セーブの実行(例外処理なし) + #-------------------------------------------------------------------------- + def save_game_without_rescue(index) + File.open(make_temp_filename, "wb") do |file| + $game_system.on_before_save + Marshal.dump(make_save_header, file) + Marshal.dump(make_save_contents, file) + @last_savefile_index = index + end + File.rename(make_temp_filename, make_filename(index)) + true + end + + #-------------------------------------------------------------------------- + # ● バックアップセーブの実行(例外処理なし) + #-------------------------------------------------------------------------- + def save_game_backup_without_rescue + File.open(make_temp_filename, "wb") do |file| + Marshal.dump(make_save_header, file) + Marshal.dump(make_save_contents, file) + end + File.rename(make_temp_filename, "Save/SaveBackup.rvdata2") + true + end + + #-------------------------------------------------------------------------- + # ● セーブ失敗時の表示名の作成 + #-------------------------------------------------------------------------- + def save_failure_word(text) + NWPatch.ver_name(" - セーブ失敗メッセージ\n\n") + "【" + text + "】" + end + + #-------------------------------------------------------------------------- + # ● セーブ失敗時の処理 + #-------------------------------------------------------------------------- + def process_save_failure1(text) + text = save_failure_word(text) + m1 = text + SaveData::FailureMes1.inject("") { |r, text| r += text + "\n" } + m2 = "不明" + begin + File.open(make_temp_filename, "wb") do |file| + make_save_header.each do |key, value| + m2 = "header " + key.to_s + Marshal.dump(value, file) + end + make_save_contents.each do |key, value| + m2 = "contents " + key.to_s + Marshal.dump(value, file) + end + end + m2 = "不明" + rescue StandardError + end + backup = text.include?("バックアップ") + m1.gsub!("<1>") { backup ? "\n" + SaveData::FailureMesBackUp : "" } + m1.gsub!("<2>") { format("セーブ失敗原因: %s", m2) } + msgbox m1 + end + + #-------------------------------------------------------------------------- + # ● サムネイル失敗時の処理 + #-------------------------------------------------------------------------- + def process_save_failure2(text) + text = save_failure_word(text) + m1 = text + SaveData::FailureMes2.inject("") { |r, text| r += text + "\n" } + backup = text.include?("バックアップ") + m1.gsub!("<1>") { backup ? "\n" + SaveData::FailureMesBackUp : "" } + end + + #-------------------------------------------------------------------------- + # ○ オートセーブの実行 + #-------------------------------------------------------------------------- + def auto_save_game + return if $game_switches[NWConst::Sw::LIBRARY_H_MEMORY] || $BTEST + + if $game_system.save_disabled + puts "★オートセーブを実行しようとしたが、セーブ不能のため無効" + return + end + puts "★オートセーブを実行" + index = "01" + begin + Dir.mkdir("Save") unless Dir.exist?("Save") + auto_save_game_without_rescue(index) + delete_temp_file + true + rescue StandardError + process_save_failure1("オート") + File.rename(make_temp_filename, make_filename(index)) if File.exist?(make_temp_filename) + delete_temp_file + false + end + end + + #-------------------------------------------------------------------------- + # ○ オートセーブの実行(例外処理なし) + #-------------------------------------------------------------------------- + def auto_save_game_without_rescue(index) + fname = make_filename(index) + File.rename(fname, make_temp_filename) if File.exist?(fname) + File.open(fname, "wb") do |file| + $game_system.on_before_save + $game_system.on_before_auto_save + Marshal.dump(make_auto_save_header, file) + Marshal.dump(make_save_contents, file) + end + + true + end + + #-------------------------------------------------------------------------- + # ● システムファイルが存在する? + #-------------------------------------------------------------------------- + def system_file_exist? + SaveSystemData.filename_set.any? { |name| File.exist?(name) } + end + + #-------------------------------------------------------------------------- + # ● システムセーブの実行 + #-------------------------------------------------------------------------- + def save_system + return false unless system_object_exist? + + begin + Dir.mkdir("Save") unless Dir.exist?("Save") + @system_save_count += 1 + save_system_without_rescue + save_system_backup_without_rescue if @system_save_count % SaveSystemData::BackUpNum == 0 + true + rescue StandardError + msgbox save_failure_word("システム") + + SaveSystemData::FailureMes1.inject("") { |r, text| r += text + "\n" } + false + end + end + + #-------------------------------------------------------------------------- + # ● システムロードの実行 + #-------------------------------------------------------------------------- + def load_system + load_system_without_rescue + rescue StandardError + msgbox save_failure_word("システム") + + SaveSystemData::FailureMes2.inject("") { |r, text| r += text + "\n" } + Kernel.exit + end + + #-------------------------------------------------------------------------- + # ● システムセーブの実行(例外処理なし) + #-------------------------------------------------------------------------- + def save_system_without_rescue + File.open(SaveSystemData.save_filename, "wb") do |file| + Marshal.dump(make_system_save_contents, file) + end + true + end + + #-------------------------------------------------------------------------- + # ● システムロードの実行(例外処理なし) + #-------------------------------------------------------------------------- + def load_system_without_rescue + File.open(SaveSystemData.load_filename, "rb") do |file| + extract_system_save_contents(Marshal.load(file)) + update_system + end + true + end + + def nw_analyze_database + [$data_actors, $data_classes, $data_skills, $data_items, $data_weapons, $data_armors, $data_enemies, + $data_states].each do |data| + data.each_with_index do |obj, i| + next unless obj + + obj.id = i + obj.note_analyze + end + end + $data_classes.compact.each do |c| + c.learnings.each { |l| l.nw_note_analyze } + end + $data_tilesets.compact.each { |t| t.nw_note_analyze } + # スクリプト素材の関係でnote消しはNG + end + def make_thumbnail + @thumbnails = {} # Hash to store thumbnails + @thumbnails.each_with_index do |(index, bitmap), i| + if bitmap.nil? + puts "Thumbnail at index #{i} is nil." + else + puts "Thumbnail at index #{i}:" + puts " Class: #{bitmap.class}" + puts " Dimensions: #{bitmap.rect.width}x#{bitmap.rect.height}" + end + end + @current_thumbnail = nil + @dummy_thumbnail = nil + + # Create current and dummy thumbnails + src_bitmap = SceneManager.background_bitmap + @current_thumbnail = Bitmap.new(160, 120) + @current_thumbnail.stretch_blt(@current_thumbnail.rect, src_bitmap, src_bitmap.rect) + + @dummy_thumbnail = Bitmap.new(160, 120) + @dummy_thumbnail.fill_rect(@dummy_thumbnail.rect, Color.new(0, 0, 0)) + + puts "Skipping thumbnail creation because the height is greater than 2000: #{@current_thumbnail.height}" + + # Ensure the Save directory exists + Dir.mkdir("Save") unless Dir.exist?("Save") + + # Process each file in the Save directory + Dir.entries("Save").each do |file_name| + puts "Checking file: #{file_name}" # Debug log + next unless file_name =~ /^Save(\d+)\.rvdata2/i # Match save file pattern + + save_number = Regexp.last_match(1) # Extract save number + thumbnail_file = "Save/Save#{save_number}.png" # Corresponding thumbnail path + + puts "Processing save file: #{file_name}, expected thumbnail: #{thumbnail_file}" + + index = save_number.to_i - 1 + + # Check if the thumbnail exists + if File.exist?(thumbnail_file) + puts "Thumbnail exists: #{thumbnail_file}" + + # Check size before indexing + # temp_bitmap = Bitmap.new(thumbnail_file) + temp_bitmap = Bitmap.new(thumbnail_file) : @dummy_thumbnail + puts "Temp Bitmap size: #{temp_bitmap.rect.width}x#{temp_bitmap.rect.height} size" + + # Add the bitmap to @thumbnails only if the size is acceptable + if temp_bitmap.rect.width > 0 && temp_bitmap.rect.height > 0 + @thumbnails[index] = temp_bitmap + puts "Successfully added thumbnail at index #{index}." + else + temp_bitmap = @dummy_thumbnail + @thumbnails[index] = temp_bitmap + + end + elsif File.exist?("Graphics/System/NotThumbnailFile.png") + puts "Thumbnail missing, using fallback: Graphics/System/NotThumbnailFile.png" + + # Check size of fallback image before indexing + fallback_bitmap = Bitmap.new("Graphics/System/NotThumbnailFile.png") + puts "Fallback Bitmap size: #{fallback_bitmap.rect.width}x#{fallback_bitmap.rect.height}" + + # Add the fallback bitmap to @thumbnails + @thumbnails[index] = fallback_bitmap + puts "Using fallback thumbnail at index #{index}." + else + temp_bitmap = @dummy_thumbnail + @thumbnails[index] = temp_bitmap + puts "Thumbnail for Save#{save_number} not found, and fallback image is missing." + end + + # Check if the thumbnail is nil or has the correct class + if @thumbnails[index].nil? + puts "Warning: Thumbnail at index #{index} is nil!" + else + puts "Thumbnail at index #{index} is of class: #{@thumbnails[index].class}" + puts "Dimensions: #{@thumbnails[index].rect.width}x#{@thumbnails[index].rect.height}" + end + end + end + + def update + $game_actors.bugfix_ignore_flag = true + $game_system.update_config + $game_party.pre_update + $game_actors.version_update + $game_party.update + $game_actors.bugfix_ignore_flag = false + end + + def update_system + $game_library.refresh_skill_update + end + + def over_map_dir + "Data/Map" + end + + def map_file_name(map_id) + div, mod = map_id.divmod(1000) + return format("Data/Map%03d.rvdata2", mod) if div == 0 + + format("#{over_map_dir}/Data/Map%03d.rvdata2", mod) + end + end +end + +module EquipInfoScene + #-------------------------------------------------------------------------- + # ● 開始処理 + #-------------------------------------------------------------------------- + def start + super + @deactivate_windows = [] + @equip_info_windows = [] + @hide_windows = [] + create_equip_info_itemname_window + create_equip_info_page_window + create_equip_info_window + @equip_info_current_window_index = 0 + @equip_info_page = 0 + @equip_info_isshow = false + end + + def update + super + equip_info_change_visible if Input.trigger?(:X) + end + + def update_all_windows + if @equip_info_isshow + if Input.trigger?(:B) + equip_info_hide + Input.update + elsif Input.trigger?(:L) || Input.trigger?(:LEFT) + equip_info_before_page + Input.update + elsif Input.trigger?(:R) || Input.trigger?(:RIGHT) + equip_info_next_page + Input.update + end + end + super + end + + #-------------------------------------------------------------------------- + # ○ 性能差比較ウィンドウの表示状態を切り替え + #-------------------------------------------------------------------------- + def equip_info_change_visible + equip_info_next_page if @equip_info_isshow + equip_info_show if !@equip_info_isshow && !equip_info_hide? + end + + #-------------------------------------------------------------------------- + # ○ 性能差比較ウィンドウの非表示 + #-------------------------------------------------------------------------- + def equip_info_hide + @equip_info_itemname_window.hide + @equip_info_page_window.hide + @equip_info_page = 0 + @equip_info_current_window_index = 0 + equip_info_windows.each { |window| window.hide } + @deactivate_windows.each(&:activate) + @hide_windows.each(&:show) + @equip_info_isshow = false + end + + #-------------------------------------------------------------------------- + # ● 性能差比較ウィンドウを非表示にするか + #-------------------------------------------------------------------------- + def equip_info_hide? + return true unless equip_info_select_item.is_a?(RPG::EquipItem) + end + + #-------------------------------------------------------------------------- + # ● 性能差比較アイテム名の文字色を明るくするか + #-------------------------------------------------------------------------- + def equip_info_enable + true + end + + def equip_info_init + @equip_info_windows = __equip_info_windows + equip_info_set_item + equip_info_set_actor + equip_info_windows.each do |window| + window.page_index = 0 + window.refresh + end + equip_info_set_page + end + + def equip_info_set_item + select_item = equip_info_select_item + @equip_info_itemname_window.item = select_item + @equip_info_windows.each { |window| window.item = select_item } + end + + def equip_info_set_page + @equip_info_page_window.page_index = equip_info_get_all_page_index[@equip_info_current_window_index] + @equip_info_page + @equip_info_page_window.page_max = equip_info_all_page_max + equip_info_window.page_index = @equip_info_page + equip_info_window.refresh if @equip_info_isshow + end + + def equip_info_set_actor + @equip_info_windows.each { |window| window.actor = equip_info_select_actor } + end + + def equip_info_select_item + nil + end + + def equip_info_select_actor + nil + end + + def create_equip_info_window + @compare_ex_window = Window_ShopCompareEx.new + @equip_info_window = Window_ShopStatusEx.new(0, 56, 640, 368) + @enchant_stone_window = Window_Enchant_Stones.new(0, 56, 640, 368) + @set_effect_window = Window_SetEffect.new(0, 56, 640, 368) + @compare_ex_window.z = 100 + @equip_info_window.z = 100 + @enchant_stone_window.z = 100 + @set_effect_window.z = 100 + @compare_ex_window.viewport = @viewport + @equip_info_window.viewport = @viewport + @enchant_stone_window.viewport = @viewport + @enchant_stone_window.viewport = @viewport + end + + def __equip_info_windows + @equip_info_windows = [@equip_info_window, @set_effect_window, @enchant_stone_window, @compare_ex_window] + end + + def create_equip_info_itemname_window + @equip_info_itemname_window = Window_ItemName.new + @equip_info_itemname_window.viewport = @viewport + end + + def create_equip_info_page_window + @equip_info_page_window = Window_Page.new + @equip_info_page_window.viewport = @viewport + end + + #-------------------------------------------------------------------------- + # ● 簡易操作説明テキスト + #-------------------------------------------------------------------------- + def show_key_text + if @equip_info_isshow + [Help.equip_info_change, + Help.equip_info_next, + Help.equip_info_hide] + else + [] + end + end + + #-------------------------------------------------------------------------- + # ● 全ページ + #-------------------------------------------------------------------------- + def equip_info_all_page_max + equip_info_windows.map(&:page_max).inject(:+) + end + + #-------------------------------------------------------------------------- + # ● ウィンドウ毎の全ページから見た初期ページ数 + #-------------------------------------------------------------------------- + def equip_info_get_all_page_index + equip_info_windows.map(&:page_max).map.with_index do |_p, index| + (equip_info_windows.map(&:page_max)[0, index] || []).inject(1) { |i, v| i + v } + end + end + + #-------------------------------------------------------------------------- + # ● 全ウィンドウ取得 + #-------------------------------------------------------------------------- + def equip_info_windows + @equip_info_windows.select { |window| window.page_max > 0 } + end + + #-------------------------------------------------------------------------- + # ● 現在の表示状態取得 + #-------------------------------------------------------------------------- + def equip_info_window_visible + equip_info_windows.any?(&:visible) + end + + #-------------------------------------------------------------------------- + # ● 次のページへ + #-------------------------------------------------------------------------- + def equip_info_next_page + @equip_info_page += 1 + if @equip_info_page >= equip_info_window.page_max + equip_info_window.hide + if @equip_info_current_window_index >= equip_info_windows.size - 1 + @equip_info_current_window_index = 0 + else + @equip_info_current_window_index += 1 + end + @equip_info_page = 0 + equip_info_window.show + + end + equip_info_set_page + end + + #-------------------------------------------------------------------------- + # ● 前のページへ + #-------------------------------------------------------------------------- + def equip_info_before_page + @equip_info_page -= 1 + if @equip_info_page == -1 + equip_info_window.hide + if @equip_info_current_window_index == 0 + @equip_info_current_window_index = equip_info_windows.size - 1 + else + @equip_info_current_window_index -= 1 + end + @equip_info_page = (equip_info_window.page_max - 1) + equip_info_window.show + end + equip_info_set_page + end + + #-------------------------------------------------------------------------- + # ● 現在起動中のウィンドウ取得 + #-------------------------------------------------------------------------- + def equip_info_window + equip_info_windows[@equip_info_current_window_index] + end + + #-------------------------------------------------------------------------- + # ○ 性能差比較ウィンドウの表示 + #-------------------------------------------------------------------------- + def equip_info_show + equip_info_init + @equip_info_isshow = true + @deactivate_windows = [] + @hide_windows = [] + instance_variables.each do |varname| + ivar = instance_variable_get(varname) + if ivar.is_a?(Window) + @deactivate_windows << ivar if ivar.active + @hide_windows << ivar if ivar.visible + end + end + @deactivate_windows.each(&:deactivate) + @hide_windows.each(&:hide) + @equip_info_page = 0 + @equip_info_current_window_index = 0 + equip_info_window.show + @equip_info_itemname_window.show + @equip_info_itemname_window.refresh + @equip_info_page_window.show + equip_info_window.refresh + end + + #-------------------------------------------------------------------------- + # ● 性能差比較ウィンドウを変更するか + #-------------------------------------------------------------------------- + def equip_info_change? + if (equip_info_select_item.nil? || !equip_info_select_item.enchant_item?) && @enchant_stone_window.visible + return true + end + return true if equip_info_select_item.nil? && @equip_info_window.visible + end +end + +module EquipInfoWindow + attr_accessor :page_index + attr_writer :item + attr_writer :actor + + def page_max + 1 + end +end + +module FavoriteItem + #-------------------------------------------------------------------------- + # ○ 選択中のアイテムのお気に入り状態を変更 + #-------------------------------------------------------------------------- + def process_set_favorite_item + Input.update + return Sound.play_buzzer if @item_window.item.nil? + + Sound.play_ok + @item_window.set_favorite_item + @item_window.refresh + @item_window.select([@item_window.index - 1, 0].max) if @item_window.favorite_mode + + @item_window.refresh + end + + #-------------------------------------------------------------------------- + # ○ お気に入り表示モードを変更 + #-------------------------------------------------------------------------- + def process_change_favorite_mode + Input.update + Sound.play_ok + last_item = @item_window.item + @item_window.change_favorite_mode + @item_window.refresh + if @item_window.active + @item_window.select_item(last_item) + else + @item_window.select(-1) + end + end + + #-------------------------------------------------------------------------- + # ○ フレーム更新 + #-------------------------------------------------------------------------- + def update + return process_set_favorite_item if enable_set_favorite_item? && Input.trigger?(:Y) + return process_change_favorite_mode if enable_change_favorite_mode? && Input.trigger?(:Z) + + @item_window.change_favorite_mode if off_favorite_mode? and @item_window.favorite_mode + super + end + + #-------------------------------------------------------------------------- + # ○ 可否 選択中のアイテムのお気に入り状態を変更 + #-------------------------------------------------------------------------- + def enable_set_favorite_item? + false + end + + #-------------------------------------------------------------------------- + # ○ 可否 お気に入り表示モードを変更 + #-------------------------------------------------------------------------- + def enable_change_favorite_mode? + false + end + + #-------------------------------------------------------------------------- + # ○ 有無 お気に入り表示モードをオフ + #-------------------------------------------------------------------------- + def off_favorite_mode? + !@item_window.visible + end +end + +module Graphics + class << self + alias hima_set_frame_count frame_count= + def frame_count=(other) + @frame_count = other.abs + hima_set_frame_count(0) + end + + alias hima_frame_count frame_count + def frame_count + hima_frame_count + @frame_count + end + + alias hima_update update + def update + hima_update + Sprite_Base.clear_checker + end +end +end +module Help + class << self + #-------------------------------------------------------------------------- + # ● アビリティ画面の上部ヘルプメッセージ + #-------------------------------------------------------------------------- + def ability + "Please choose an ability type" + end + + #-------------------------------------------------------------------------- + # ● アビリティ画面の下部ヘルプメッセージ + #-------------------------------------------------------------------------- + def ability_key + "#{Vocab.key_c}:Select #{Vocab.key_b}:Cancel #{Vocab.key_a}:Remove" + end + + #-------------------------------------------------------------------------- + # ● スキル画面の下部ヘルプメッセージ + #-------------------------------------------------------------------------- + def skill_type_key + t = "" + t += "\\C[0]" + t += "#{Vocab.key_a}:Hide in battle + Disable in auto mode" + t += "(Disabled)" unless $game_system.conf[:bt_stype] + t += "\n" + t += "\\C[16]" if Input.press?(:X) + t += "#{Vocab.key_x}+↑/↓:Sort by skill type + t += "#{Vocab.key_x}+#{Vocab.key_b}:Reset skill order" + t + end + + #-------------------------------------------------------------------------- + # ● コンフィグ-色調のヘルプメッセージ + #-------------------------------------------------------------------------- + def config_tone + [ + "Set red value.\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "Set green value.\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "Set blue value.\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "Restore defaults.", + "Return" + ] + end + + #-------------------------------------------------------------------------- + # ● コンフィグ-音量のヘルプメッセージ + #-------------------------------------------------------------------------- + def config_sound + [ + "BGM Volume\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "BGS Volume\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "ME Volume\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "SE Volume\r\n←/→:-/+#{Vocab.key_l}/#{Vocab.key_r}:Big -/+", + "Restore defaults.", + "Return." + ] + end + + #-------------------------------------------------------------------------- + # ● パーティ編成画面のヘルプメッセージ + #-------------------------------------------------------------------------- + def party_edit + [ + "#{Vocab.key_a}:Remove", + "#{Vocab.key_x}:Status", + "#{Vocab.key_y}:Sort", + "#{Vocab.key_z}:Warp To", + ] + end + + #-------------------------------------------------------------------------- + # ● 転職画面のヘルプメッセージ + #-------------------------------------------------------------------------- + def job_change + ["#{Vocab.key_y}/#{Vocab.key_z}:Sort"] + end + + #-------------------------------------------------------------------------- + # ● スロット画面の操作説明テキスト + #-------------------------------------------------------------------------- + def slot_description + { + :stand => "→:Increase Wager #{Vocab.key_c}:Spin Slots\n←:Decrease Wager #{Vocab.key_b}:Quit", + :play => "#{Vocab.key_c}:Stop Reel" + } + end + + #-------------------------------------------------------------------------- + # ● ショップ画面の装備品情報変更テキスト + #-------------------------------------------------------------------------- + def shop_equip_change + "←/→:Info Change" + end + + #-------------------------------------------------------------------------- + # ● ショップ画面の装備品性能比較テキスト + #-------------------------------------------------------------------------- + def shop_param_compare + "#{Vocab.key_x}:Stat Change" + end + + #-------------------------------------------------------------------------- + # ● 性能差比較 装備情報ウインドウのボタン説明1 + #-------------------------------------------------------------------------- + def item_info_change + "#{Vocab.key_x}:Info Change" + end + + #-------------------------------------------------------------------------- + # ● 性能差比較 装備情報ウインドウのボタン説明2 + #-------------------------------------------------------------------------- + def item_info_exit + "#{Vocab.key_x}:Close" + end + + #-------------------------------------------------------------------------- + # ● 図鑑画面のヘルプメッセージ + #-------------------------------------------------------------------------- + def library + { + :blank => "This entry is blank.", + :discovery => "This entry's details are unknown.", + :return_top => "Return to top.", + :close_lib => "Close and return to last screen.", + :btn_detail => "#{Vocab.key_c}:View Details", + :btn_column => "↑/↓:Select", + :btn_jump => "#{Vocab.key_l}/#{Vocab.key_r}:Jump", + :btn_page => "←/→:Page", + :btn_scroll => "#{Vocab.key_y}/#{Vocab.key_z}:Scroll Text", + :btn_equip => "#{Vocab.key_x}:Equip Info" + } + end + + #-------------------------------------------------------------------------- + # ● 装備品情報変更テキスト + #-------------------------------------------------------------------------- + def equip_info_change + "←/→,#{Vocab.key_l}/#{Vocab.key_r}:Switch info page" + end + + #-------------------------------------------------------------------------- + # ● 装備品情報変更テキスト + #-------------------------------------------------------------------------- + def equip_info_next + "#{Vocab.key_x}:Next page" + end + + #-------------------------------------------------------------------------- + # ● 装備品情報変更テキスト + #-------------------------------------------------------------------------- + def equip_info_hide + "#{Vocab.key_b}:Close info page" + end + end +end + +#============================================================================== +# ■ SceneManager +#============================================================================== +module SceneManager + class << self + attr_writer :background_bitmap + attr_accessor :stack + + #-------------------------------------------------------------------------- + # ○ 実行 + #-------------------------------------------------------------------------- + def run + Audio.setup_midi if use_midi? + # ~ Audio.init_basic + Audio.reset_sound + DataManager.init + LibraryH::Manager.init + @scene = first_scene_class.new + @scene.main while @scene + end + + #-------------------------------------------------------------------------- + # ● シーンスタックへのプッシュ + #-------------------------------------------------------------------------- + def push(scene) + @stack.push(scene.new) + end + + #-------------------------------------------------------------------------- + # ○ 背景として使うためのスナップショット作成 + #-------------------------------------------------------------------------- + def snapshot_for_background + @background_bitmap.dispose if @background_bitmap + @background_bitmap = Graphics.snap_to_bitmap + # @background_bitmap.blur + end + end +end + +#============================================================================== +# ■ ShowKey_Help +#============================================================================== +module ShowKey_Help + class << self + #-------------------------------------------------------------------------- + # ● 各画面 + #-------------------------------------------------------------------------- + def lr_scroll + "#{Vocab.key_l}/#{Vocab.key_r}:Page Scroll" + end + + #-------------------------------------------------------------------------- + # ● アイテムを表示する画面 + #-------------------------------------------------------------------------- + def favorite_item(state) + "#{Vocab.key_y}:#{state ? "Remove" : "Save"} favorite" + end + + #-------------------------------------------------------------------------- + # ● アイテムを表示する画面 + #-------------------------------------------------------------------------- + def favorite_mode(mode) + "#{Vocab.key_z}:#{mode ? "Display all but" : "Display only"} favorites" + end + + #-------------------------------------------------------------------------- + # ● 装備を表示する画面 + #-------------------------------------------------------------------------- + def equip_info + "#{Vocab.key_x}:Equip Info" + end + + #-------------------------------------------------------------------------- + # ● キャラごとの画面 + #-------------------------------------------------------------------------- + def lr_actor + "#{Vocab.key_l}/#{Vocab.key_r}:Switch Character" + end + + #-------------------------------------------------------------------------- + # ● スキル画面 + #-------------------------------------------------------------------------- + def stype_disable + "#{Vocab.key_a}:Hide in Battle + Disable in Auto Mode" + end + + #-------------------------------------------------------------------------- + # ● スキル画面 + #-------------------------------------------------------------------------- + def stype_move + "#{Vocab.key_x}+↑↓:Sort" + end + + #-------------------------------------------------------------------------- + # ● 装備画面 + #-------------------------------------------------------------------------- + def equip_shift + "#{Vocab.key_y}:Remove Equip" + end + + def equip_stone + "#{Vocab.key_a}:Switch Gem" + end + #-------------------------------------------------------------------------- + # ● アビリティ画面 + #-------------------------------------------------------------------------- + def ability_shift_all + "#{Vocab.key_a}:Remove all Abilities" + end + + #-------------------------------------------------------------------------- + # ● アビリティ画面 + #-------------------------------------------------------------------------- + def ability_shift + "#{Vocab.key_a}:Remove Ability" + end + + #-------------------------------------------------------------------------- + # ● ショップ画面の装備品情報変更テキスト + #-------------------------------------------------------------------------- + def shop_equip_change + Help.shop_equip_change + end + + #-------------------------------------------------------------------------- + # ● ショップ画面の装備品性能比較テキスト + #-------------------------------------------------------------------------- + def shop_param_compare + Help.shop_param_compare + end + + #-------------------------------------------------------------------------- + # ● 秘石画面 + #-------------------------------------------------------------------------- + def stone_shift_all + "#{Vocab.key_a}:Remove All Gems" + end + + #-------------------------------------------------------------------------- + # ● 秘石画面 + #-------------------------------------------------------------------------- + def stone_shift + "#{Vocab.key_a}:Remove Gem" + end + + def stone_change_scene + "←/→:Gem Equip Screen" + end + end +end + +module ShowKey_HelpWindow + def post_start + create_show_key_sprite + super + end + + #-------------------------------------------------------------------------- + # ● 簡易操作説明スプライトの作成 + #-------------------------------------------------------------------------- + def create_show_key_sprite + @show_key_sprite = Sprite_ShowKey.new(show_key_sprite_window) + update_show_key_sprite + end + + #-------------------------------------------------------------------------- + # ● 簡易操作説明の更新 + #-------------------------------------------------------------------------- + def set_text_show_key_sprite(text) + @show_key_sprite.set_text(text) + end + + #-------------------------------------------------------------------------- + # ● 簡易操作説明ウィンドウ + #-------------------------------------------------------------------------- + def show_key_sprite_window + @help_window + end + + #-------------------------------------------------------------------------- + # ● フレーム更新 + #-------------------------------------------------------------------------- + def update + super + update_show_key_sprite + end + + #-------------------------------------------------------------------------- + # ● 簡易操作説明の更新 + #-------------------------------------------------------------------------- + def update_show_key_sprite + set_text_show_key_sprite(show_key_text) + end + + #-------------------------------------------------------------------------- + # ● 終了処理 + #-------------------------------------------------------------------------- + def terminate + @show_key_sprite.dispose + super + end +end + +#============================================================================== +# ■ Vocab +#============================================================================== +module Vocab + #-------------------------------------------------------------------------- + # ○ 基本挿替 + #-------------------------------------------------------------------------- + # Evasion = "しかし%sは素早くかわした!" + ActorNoHit = "But %s quickly dodged!" + EnemyNoHit = "But %s quickly dodged!" + ActorNoDamage = "%s took no damage!" + EnemyNoDamage = "%s took no damage!" + Block = "%s blocked the attack with their shield!" + #-------------------------------------------------------------------------- + # ● ユーザ定義 + #-------------------------------------------------------------------------- + Giveup = [ + "Luka yields to temptation and stops fighting!", + "His companions desert and leave him to his fate..." + ] + BindingStart = [ + "%s is bound!", + "%s is being raped!", + "%s is being raped!", + "But %s is already bound!" + ] + TemptationActionFailure = "But Luka has already been defeated!" + + Ability = "Ability" + Shortage = "But there isn't enough %s!" + SkillSealedFailure = "But that skill is sealed!" + ObtainJobExp = "%s job XP gained." + Stealed = "Stole %s from %s!" + StealFailure = "Couldn't steal anything from %s!" + StealedItemEmpty = "%s has nothing to steal!" + Stand = "%s refused to admit defeat!" + Invalidate = "It had no effect on %s!" + DefenseWall = "%s was defended by a wall!" + PayLife = "%s was spent!" + PayLifeFailure = "%s was debilitated!" + OverDriveSuccess = "%s stopped time!" + OverDriveFailure = "But time was already stopped!" + BindResistSuccess = "...and escaped from %s's restraint!" + BindResistFailure = "...but couldn't escape from %s's restraint!" + EternalBindResist = "...but is still being held down by %s!" + PleasureFinished = " came!" + Predation = "%s was devoured!" + ReStoration = "%s absorbed %s!" + ThrowItem = "%s threw a %s!" + PhysicalReflection = "%s reflects the attack!" + NotObtainItem = "%s obtained but you can't carry any more..." + ACTOR_DAMAGE = "%s takes %s %s damage!" + ENEMY_DAMAGE = "%s takes %s %s damage!" + STATE_BOOST = "Condition bonus!!" + EX_CATEGORY_BOOST = "Slayer bonus!!" + + AUTOBATTLE = "Auto" + BATTLE_STATES = "Status" + ENCHANT_STONE = "Gem" + ClassLevel = "Job Level" + TribeLevel = "Race Level" + + module AutoBattle + NORMAL = "Random" + NOT_MP_SKILL = "Conserve MP" + REPEAT = "Repeat" + ATTACK_ONLY = "Normal Attack" + end + + PartySaveMessage = "Which slot do you want to register a party?" + PartyLoadMessage = "Which party do you want to call?" + + class << self + # 能力値 (短) + def params_a(param_id) + ["ATK", "DEF", "MAG", "WIL", "AGI", "DEX"][param_id] + end + + # 仮想キー + def key_a + { :gamepad => "1", :keyboard => "Shift" }[$game_system.conf[:key_text]] + end + + def key_b + { :gamepad => "2", :keyboard => "X" }[$game_system.conf[:key_text]] + end + + def key_c + { :gamepad => "3", :keyboard => "Z" }[$game_system.conf[:key_text]] + end + + def key_x + { :gamepad => "4", :keyboard => "A" }[$game_system.conf[:key_text]] + end + + def key_y + { :gamepad => "5", :keyboard => "S" }[$game_system.conf[:key_text]] + end + + def key_z + { :gamepad => "6", :keyboard => "D" }[$game_system.conf[:key_text]] + end + + def key_l + { :gamepad => "7", :keyboard => "Q" }[$game_system.conf[:key_text]] + end + + def key_r + { :gamepad => "8", :keyboard => "W" }[$game_system.conf[:key_text]] + end + + # パーティコマンド + def giveup + "Give Up" + end + + def shift_change + "Party" + end + + def library + "Library" + end + + def config + "Config" + end + + def all_attack + "Auto Atk" + end + + def item_get_message(item, value, is_display_value = false) + message = item.window_name + if value == 0 + format(NotObtainItem, message) + else + #Below was reordered so the number is leading(before the item that is obtained) + format(ObtainItem,(item.enchant_item? || !is_display_value ? "":" #{value}") + message ) + end + end + end +end diff --git a/Kawariki-patches/ports/dummyAudioUtilities.rb b/Kawariki-patches/ports/dummyAudioUtilities.rb new file mode 100644 index 0000000..cda972c --- /dev/null +++ b/Kawariki-patches/ports/dummyAudioUtilities.rb @@ -0,0 +1,1351 @@ +=begin +This script contains various utility functions and classes for dealing +with audio. This is a stand-alone script. + +Audio.square(durationInMs,freq,volume,timbre,async) - Generates a square wave. +Audio.beep(durationInMs,freq,volume,timbre,async) - Alias for Audio.square +Audio.sine(durationInMs,freq,volume,timbre,async) - Generates a sine wave. +Audio.triangle(durationInMs,freq,volume,timbre,async) - Generates a triangle wave. +Audio.saw(durationInMs,freq,volume,async) - Generates a saw wave. +Audio.noise(durationInMs,volume,async) - Generates white noise. +Audio.playTone(toneFile,async) - Plays a tone in the Apple iPod alarm tone format. +Parameters: + durationInMs - duration of the sound in milliseconds. + The module Audio::NoteLength contains useful durations for tones. + If 0 or nil, the frequency is determined using the maximum duration + of the given sound envelopes. + freq - the frequency of the sound in Hz. The higher the frequency, + the higher the pitch. If 0, no sound will be generated. + The module Audio::Note contains useful frequencies for tones. + freq can also be a SoundEnvelope or an array of two element arrays, + as follows: + freq[0] - time in ms to apply the specified frequency + freq[1] - frequency to apply. In between, values will be interpolated + volume - volume of the sound, from 0 through 100 + volume can also be a SoundEnvelope. + async - specifies whether the function will return immediately + without waiting for the sound to finish (stands for asynchronous) + timbre - specifies the timbre of the tone; from 0.0 through 1.0 + timbre can also be a SoundEnvelope or an array of two element arrays, + as follows: + volume[0] - time in ms to apply the specified timbre + volume[1] - timbre to apply. In between, values will be interpolated + +WaveData - A class for holding audio data in memory. This class +is easy to serialize into the save file. + intensity() - Calculates the intensity, or loudness of the data + Returns a value from 0 through 127. + time() - Length of the data in seconds. + play() - Plays the wave data + +getPlayTime(filename) - Gets the length of an audio file in seconds. + Supports WAV, MP3, and OGG files. +getWaveData(filename) - Creates wave data from the given WAV file path. + Returns a WaveData object or an integer: 1=not found; 2=invalid format; + 3=format not supported; 4=no sound in the data (the last error is helpful + for diagnosing whether anything was recorded, since a recording device + can record even if no microphone is attached.) + +beginRecord() - Starts recording. Returns 0 if successful. +getRecorderSample() - Gets a single sample from the microphone. + The beginRecord function must have been called beforehand. +stopRecord() - Stops recording without saving the recording to a file. +endRecord(file) - Stops recording and saves the recording to a file. +=end + +if !defined?(safeExists?) + def safeExists?(f) + ret=false + File.open(f,"rb") { ret=true } rescue nil + return ret + end +end + +def pbSaveSoundData(samples, freq, filename) + samples="" if !samples + data=[ + 0x46464952,samples.length+0x2C, + 0x45564157,0x20746d66,0x10, + 0x01,0x01, # PCM,mono + freq,freq, + 1,8, # 8-bit + 0x61746164,samples.length + ].pack("VVVVVvvVVvvVV") + f=File.open(filename,"wb") + if f + f.write(data) + f.write(samples) + f.close + end +end + +# plays 8 bit mono sound data (default: 11025 Hz) +def pbPlaySoundData(samples,volume,async=false,sampleFreq=11025) + return if !samples || samples.length==0 || sampleFreq==0 + waveOutOpen=Win32API.new("winmm.dll","waveOutOpen","plplll","l") + waveOutPrepareHeader=Win32API.new("winmm.dll","waveOutPrepareHeader","lpl","l") + waveOutWrite=Win32API.new("winmm.dll","waveOutWrite","lpl","l") + waveOutSetVolume=Win32API.new("winmm.dll","waveOutSetVolume","ll","l") + waveOutClose=Win32API.new("winmm.dll","waveOutClose","l","l") + waveOutGetNumDevs=Win32API.new("winmm.dll","waveOutGetNumDevs","","l") + getStringAddress=proc {|obj| + return 0 if !obj + buffer=" "*4 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + stringPointer=(obj.__id__*2)+12 + rtlMoveMemory_pi.call(buffer,stringPointer,4) + return buffer.unpack("L")[0] + } + saveToTemp=proc {|samples,freq| + chars="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + for i in 1...1000 + name=""; 8.times { name+=chars[rand(chars.length),1] } + name=ENV["TEMP"]+"\\"+name+"_tmp.wav" + if !safeExists?(name) + pbSaveSoundData(samples,freq,name) + return name + end + end + return nil + } + playThenDelete=proc {|path,volume,length,async| + return if !path || !safeExists?(path) + thread=Thread.new { + Thread.stop + _path=Thread.current[:path] + _length=Thread.current[:length] + sleep(_length); + File.delete(_path) rescue nil + } + thread[:path]=path + thread[:length]=length + Audio.se_play(path,volume) + thread.run + sleep(length); + } + waveHdr=[getStringAddress.call(samples),samples.length, + 0,0,0,0,0,0].pack("V*") + # 8 bit mono sound data + waveFormat=[0x01,0x01,sampleFreq,sampleFreq,1,8,0].pack("vvVVvvv") + duration=samples.length + waveOutHandle=" "*4 + code=waveOutOpen.call(waveOutHandle,-1,waveFormat,0,0,0) + if code!=0 + timeLength=samples.length.to_f/sampleFreq + path=saveToTemp.call(samples,sampleFreq) + playThenDelete.call(path,volume,timeLength,async) + return + end + waveOutHandle=waveOutHandle.unpack("L")[0] + volume=(volume*65535/100) + volume=(volume<<16)|volume + waveOutSetVolume.call(waveOutHandle,volume) + code=waveOutPrepareHeader.call(waveOutHandle,waveHdr,waveHdr.length) + if code!=0 + waveOutClose.call(waveOutHandle) + return + end + thread=Thread.new { + Thread.stop + waveOut=Thread.current[:waveOut] + waveHdr=Thread.current[:waveHeader] + waveData=Thread.current[:waveData] + waveOutUnprepareHeader=Win32API.new("winmm.dll","waveOutUnprepareHeader","lpl","l") + waveOutClose=Win32API.new("winmm.dll","waveOutClose","l","l") + loop do + sleep(1) + hdr=waveHdr.unpack("V*") + flags=hdr[4] + if (flags&1)==1 + # All done + waveOutUnprepareHeader.call(waveOut,waveHdr,waveHdr.length) + waveOutClose.call(waveOut) + break + end + end + } + thread[:waveOut]=waveOutHandle + thread[:waveHeader]=waveHdr + thread[:waveData]=@samples + if waveOutWrite.call(waveOutHandle,waveHdr,waveHdr.length)!=0 + waveOutClose.call(waveOutHandle) + return + end + thread.run + sleep(@duration/1000.0) if !async + return +end + + + +class NoteEnvelope + attr_accessor :fall + attr_accessor :max + attr_reader :envelope + + def initialize(fall=1200,maxPoint=30) + @fall=fall # time until fall to zero + @maxPoint=maxPoint # maximum point + @envelope=SoundEnvelope.new + end + + def falling(duration) + return self if duration<=0 + @envelope.changeDiscrete(0,@maxPoint) + if duration>=@fall + @envelope.change(fall,0) + @envelope.change(duration-fall,0) + else + @envelope.change(duration,@maxPoint*duration/@fall) + end + @envelope.changeDiscrete(0,0) + return self + end + + def sweeping(duration,sweepDuration) + return self if duration<=0 + return steady(duration) if sweepDuration<=0 + @envelope.changeDiscrete(0,@maxPoint) + falling=true + while duration>0 + dur=duration>sweepDuration ? sweepDuration : duration + if falling + self.falling(dur) + else + sd=sweepDuration + if sd>@fall + d=[(sweepDuration-@fall),dur].min + @envelope.change(d,0) + dur-=d + sd-=d + end + if d==sd + @envelope.change(dur,@maxPoint) + else + @envelope.change(dur,@maxPoint*(@fall-(sd-dur))/@fall) + end + end + falling=!falling + duration-=sweepDuration + end + @envelope.changeDiscrete(0,0) + return self + end + + def rest(duration) + if duration>0 + @envelope.changeDiscrete(0,0) + @envelope.changeDiscrete(duration,0) + end + return self + end + + def steady(duration) + if duration>0 + @envelope.changeDiscrete(0,@maxPoint) + @envelope.changeDiscrete(duration,@maxPoint) + @envelope.changeDiscrete(0,0) + end + return self + end +end + + + +# A class for holding audio data in memory. This class +# is easy to serialize into the save file. +class WaveData + def initialize(samplesPerSec,samples) + @freq=samplesPerSec + @samples=samples.is_a?(String) ? samples.clone : samples.pack("C*") + end + + def setSamples(samples) + @samples=samples.is_a?(String) ? samples.clone : samples + end + + def self._load(string) + data=Marshal.load(string) + ret=self.new(data[0],[]) + ret.setSamples(Zlib::Inflate.inflate(data[1])) + return ret + end + + def _dump(depth=100) + return Marshal.dump([@freq,Zlib::Deflate.deflate(@samples)]) + end + + def intensity + distance=@samples.length/2000 + i=distance/2 + count=0 + volume=0 + while i<@samples.length + vol=(@samples[i]-128).abs + vol=127 if vol>127 + if vol>=16 + volume+=vol + count+=1 + end + i+=distance + end + return 0 if count==0 + return volume/count # from 0 through 127 + end + + def time + return @freq==0 ? 0.0 : (@samples.length)*1.0/@freq + end + + def play + # Play sound data asynchronously + pbPlaySoundData(@samples,100,true,@freq) + end + + def save(filename) + pbSaveSoundData(@samples,@freq,filename) + end +end + + + +# A class for specifying volume, frequency, and timbre envelopes. +class SoundEnvelope; include Enumerable + def initialize(env=nil) + @e=[]; + set(env) if env + end + + def self.fromToneFile(file) + envelope=self.new + File.open(file,"rb"){|f| + f.gets if !f.eof? + while !f.eof? + ln=f.gets + if ln[ /^(\d+)\s+(\d+)/ ] + envelope.addValueChange($1.to_i,$2.to_i) + end + end + } + return envelope + end + + def length; @e.length; end + def [](x); @e[x]; end + def each; @e.each {|x| yield x }; end + def clear; @e.clear; end + + def changeAbsolute(pos,volume) + return self if pos<0 + velength=@e.length + if velength>0 + @e.push([pos,volume]) + stableSort() + else + @e.push([pos,volume]) + end + return self + end + + def self.initial(value) + return self.new.change(0,value) + end + + def self.smoothVolume(duration,volume) + env=self.new + return env if duration<8 + env.change(0,0) + env.change(5,volume) + env.changeAbsolute(duration-10,volume) + env.changeAbsolute(duration-5,0) + env.changeAbsolute(duration,0) + return env + end + + # Creates a volume envelope using the given attack, decay, and + # release times and the given sustain level. + # duration - duration of the sound + # attack - attack time (in ms), or time between the start of the sound + # and the time where the sound reaches its maximum volume. + # If this value is less than 0, the sound will decay from silence to + # the sustain volume instead (see below). + # decay - decay time (in ms), or time after the attack phase until + # the time where the sound reaches its sustain volume + # sustain - sustain volume, or normal volume of sound (0-100) + # release - release time (in ms), or amount of time to fade out the + # sound when it reaches its end. The sound's duration includes its + # release time. + def self.attackDecayRelease(duration,attack,decay,sustain,release) + env=self.new + if attack<=0 + env.change(0,attack==0 ? 100 : 0) + else + env.change(attack,100) + end + env.change(decay,sustain) + env.changeAbsolute(duration-release,sustain) + if release>20 + env.changeAbsolute(duration-release-4,0) + end + env.changeAbsolute(duration,0) + return env + end + + def self.blink(value,onDuration,offDuration,totalDuration) + return self.new.addValueChanges( + value,onDuration,0,offDuration).repeat(totalDuration) + end + + def change(delta,volume) + return self if delta<0 + velength=@e.length + if velength>0 + @e.push([@e[velength-1][0]+delta,volume]) + else + @e.push([delta,volume]) + end + return self + end + + def duration + return @e.length==0 ? 0 : @e[@e.length-1][0] + end + + def value + return @e.length==0 ? 0 : @e[@e.length-1][1] + end + + def addValueChange(value,duration) + changeDiscrete(0,value) + changeDiscrete(duration,value) + return self + end + + def sweep(value1,value2,duration,sweepDuration=1000/16) + val=true + while duration>0 + dur=durationdesiredDuration + deltaNew=(newDuration-self.duration) + deltaDesired=(desiredDuration-self.duration) + newValue=deltaNew==0 ? self.value : self.value+(item[1]-self.value)*deltaDesired/deltaNew + @e.push([desiredDuration,newValue]) + break + else + @e.push([newDuration,item[1]]) + end + i+=1 + if i>=oldLength + i=0; currentDuration+=oldDuration + end + end + return self + end + + # Changes the volume, frequency, etc. abruptly without blending. + def changeDiscrete(delta,volume) + return self if delta<0 + velength=@e.length + if velength>0 + oldValue=@e[velength-1][1] + oldDelta=@e[velength-1][0] + newDelta=oldDelta+delta + newValue=oldValue + if newDelta!=oldDelta || newValue!=oldValue + @e.push([newDelta,newValue]) + oldDelta=newDelta + oldValue=newValue + end + newValue=volume + if newDelta!=oldDelta || newValue!=oldValue + @e.push([newDelta,newValue]) + end + else + @e.push([delta,volume]) + end + return self + end + + def set(value) + if value.is_a?(SoundEnvelope) || value.is_a?(Array) + @e.clear + for v in value; @e.push(v); end + end + return self + end + + private + + def stableSort + pm=1;while pm<@e.length + pl=pm; while pl>0 && @e[pl-1][0]>@e[pl][0] + tmp=@e[pl]; @e[pl]=@e[pl-1]; @e[pl-1]=tmp + pl-=1; end + pm+=1;end + end +end + + + +# internal class +class SoundEnvelopeIterator# :nodoc: + def initialize(env) + @env=env + @envIndex=0 + end + + def getValue(frame) + value=0 + if @envIndex==@env.length + value=@env[@envIndex-1][1] + elsif @envIndex==0 + value=@env[@envIndex][1] + else + lastPos=@env[@envIndex-1][0] + thisPos=@env[@envIndex][0] + if thisPos!=lastPos + lastVolume=@env[@envIndex-1][1] + thisVolume=@env[@envIndex][1] + value=(thisVolume-lastVolume)*(frame-lastPos)/(thisPos-lastPos)+lastVolume + else + value=@env[@envIndex][1] + end + end + while @envIndex+1<=@env.length && @env[@envIndex][0]==frame.to_i + @envIndex+=1 + end + return value + end +end + + + +# internal class +class WaveForm# :nodoc: + SAMPLEFREQ=11025 + + def initialize(proc,freq,duration,timbre=0.5) + @duration=duration # in ms + @volumeEnvelope=SoundEnvelope.new + @freqEnvelope=SoundEnvelope.new + @timbreEnvelope=SoundEnvelope.new + @proc=proc + @freq=freq + @timbre=timbre + if @freq.is_a?(Array) || @freq.is_a?(SoundEnvelope) + @freqEnvelope.set(@freq) + @freq=@freq.length>0 ? @freq[0][1] : 800 + end + if @timbre.is_a?(Array) || @timbre.is_a?(SoundEnvelope) + @timbreEnvelope.set(@timbre) + @timbre=@timbre.length>0 ? @timbre[0][1] : 0.5 + end + end + + def setFrequencyEnvelope(env) + if env.is_a?(Numeric) + @freqEnvelope.clear + @freqEnvelope.addValueChange(env,@duration) + else + @freqEnvelope.set(env) + end + end + + def setVolumeEnvelope(env) + if env.is_a?(Numeric) + @volumeEnvelope.clear + @volumeEnvelope.addValueChange(env,@duration) + else + @volumeEnvelope.set(env) + end + end + + def lcm(x,y) + return y if x==0; return x if y==0 + return x if x==y + if x>y; incr=x + while x%y!=0; x+=incr; end + return x + else; incr=y + while y%x!=0; y+=incr; end + return y + end + end + + def start + @i=0 + @volumeIterator=SoundEnvelopeIterator.new(@volumeEnvelope) + @freqIterator=SoundEnvelopeIterator.new(@freqEnvelope) + @timbreIterator=SoundEnvelopeIterator.new(@timbreEnvelope) + @sampleCount=@duration*SAMPLEFREQ/1000 + @samples=" "*@sampleCount + @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max + @step=1.0/@exactSamplesPerPass + @exactCounter=0 + end + + def self.frac(x) + return x-x.floor + end + + def nextSample + vol=100 + fframe=@i*1000.0/SAMPLEFREQ + if @volumeEnvelope.length>0 # Update volume + vol=@volumeIterator.getValue(fframe) + end + if @proc + updateBuffer=false + if @freqEnvelope.length>0 # Update frequency + freq=@freqIterator.getValue(fframe) + if freq!=@freq # update sample buffer + @freq=freq + @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max + @step=1.0/@exactSamplesPerPass + end + end + if @timbreEnvelope.length>0 # Update timbre + @timbre=@timbreIterator.getValue(fframe) + end + if @freq==0 || vol==0 + @samples[@i]=0x80 + else + sample=@proc.call(@exactCounter,@timbre) + @samples[@i]=0x80+(vol*sample).round + end + else # noise + v=vol.abs.to_i*2 + @samples[@i]=0x80+(rand(v).to_i-(v/2)) + end + @i+=1 + @exactCounter+=@step + while @exactCounter>1.0; @exactCounter-=1.0; end + end + + def mixSamples(other) + for i in 0...other.sampleCount + newSamp=((@samples[i]-0x80)+(other.samples[i]-0x80))/2+0x80 + @samples[i]=newSamp + end + end + + def play(volume=100, async=false) + pbPlaySoundData(@samples,volume,async,SAMPLEFREQ) + end + + def generateSound + start + while @i<@sampleCount + nextSample + end + end + + def toWaveData + return WaveData.new(SAMPLEFREQ,@samples) + end + + def save(filename) + pbSaveSoundData(@samples,SAMPLEFREQ,filename) + end + + attr_accessor :samples,:sampleCount +end + + + +module Audio + module Note + REST = 0 + GbelowC = 196 + A = 220 + Asharp = 233 + B = 247 + C = 262 + Csharp = 277 + D = 294 + Dsharp = 311 + E = 330 + F = 349 + Fsharp = 370 + G = 392 + Gsharp = 415 + end + + module NoteLength + WHOLE = 1600 + HALF = WHOLE/2 + QUARTER = HALF/2 + EIGHTH = QUARTER/2 + SIXTEENTH = EIGHTH/2 + end + + def self.noise(durationInMs=200, volume=100, async=false) + return if durationInMs<=0 + waveForm=WaveForm.new(nil,0,durationInMs) + if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) + waveForm.setVolumeEnvelope(volume) + volume=100 + end + waveForm.generateSound + waveForm.play(volume,async) + end + + # internal method + def self.envelopeDuration(env) + return 0 if !env + if env.is_a?(SoundEnvelope) || env.is_a?(Array) + return SoundEnvelope.new(env).duration + end + return 0 + end + + def self.oscillateDouble(durationInMs, freq1, freq2, volume1, volume2, + timbre1, timbre2, proc1, proc2, async=false) + return if durationInMs<0 + freq1Zero=(freq1.is_a?(Numeric) && freq1<=0) || + (freq1.is_a?(Array) && freq1.length==0) + freq2Zero=(freq2.is_a?(Numeric) && freq2<=0) || + (freq2.is_a?(Array) && freq2.length==0) + if freq1Zero && freq2Zero + Thread.sleep(durationInMs/1000.0) if !async + return + end + if durationInMs==0 || durationInMs==nil + durationInMs=[ + envelopeDuration(freq1), + envelopeDuration(freq2), + envelopeDuration(volume1), + envelopeDuration(volume2), + envelopeDuration(timbre1), + envelopeDuration(timbre2) + ].max + return if durationInMs<=0 + end + waveForm1=WaveForm.new(proc1,freq1,durationInMs,timbre1) + waveForm2=WaveForm.new(proc2,freq2,durationInMs,timbre2) + waveForm1.setVolumeEnvelope(volume1) + waveForm2.setVolumeEnvelope(volume2) + waveForm1.generateSound + waveForm2.generateSound + waveForm1.mixSamples(waveForm2) + waveForm1.play(100,async) + end + + def self.oscillate(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false, &block) + return if durationInMs<0 + if (freq.is_a?(Numeric) && freq<=0) || (freq.is_a?(Array) && freq.length==0) + Thread.sleep(durationInMs/1000.0) if !async + return + end + if durationInMs==0 || durationInMs==nil + durationInMs=[ + envelopeDuration(freq), + envelopeDuration(volume), + envelopeDuration(timbre)].max + return if durationInMs<=0 + end + waveForm=WaveForm.new(block,freq,durationInMs,timbre) + if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) + waveForm.setVolumeEnvelope(volume) + volume=100 + end + waveForm.generateSound + waveForm.play(volume,async) + end + + def self.frac(x) + return x-x.floor + end + + TWOPI=Math::PI*2 + @@sineProc2=proc {|z,timbre| + x=z1 + for i in 0...tone.length + dtmf(tone[i,1],durationInMs,volume,false) + end + end + t1=0 + t1=1209 if "14ghi7pqrs*".include?(tone) + t1=1336 if "2abc5jkl8tuv0".include?(tone) + t1=1477 if "3def6mno9wxyz#".include?(tone) + t1=1633 if "ABCD".include?(tone) + t2=0 + t2=697 if "12abc3defA".include?(tone) + t2=770 if "4ghi5jkl6mnoB".include?(tone) + t2=852 if "7pqrs8tuv9wxyzC".include?(tone) + t2=941 if "*0#D".include?(tone) + return if t1==0 || t2==0 + f1=Math::PI*2.0*t1/WaveForm::SAMPLEFREQ + f2=Math::PI*2.0*t2/WaveForm::SAMPLEFREQ + doubleSine(durationInMs,t1,t2,volume,volume,0.5,0.5,async) + end + + def self.beep(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false) + square(durationInMs,freq,volume,timbre,async) + end + + @@triangleProc2=proc {|z,timbre| + z>4 + next if t==0 || t==15 + freqs=[44100,22050,11025,48000] + bitrates=[32,40,48,56,64,80,96,112,128,160,192,224,256,320] + bitrate=bitrates[t] + t=(rstr[1]>>2)&3 + freq=freqs[t] + t=(rstr[1]>>1)&1 + filesize=FileTest.size(filename) + frameLength=((144000*bitrate)/freq)+t + numFrames=filesize/(frameLength+4) + time=(numFrames*1152.0/freq) + break + end + end + } + return time +end + +# Creates wave data from the given WAV file path +def getWaveData(filename) + time=-1 + fgetdw=proc{|file| + (file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0)) + } + fgetw=proc{|file| + (file.eof? ? 0 : (file.read(2).unpack("v")[0] || 0)) + } + if !safeExists?(filename) + return 1 # Not found + end + File.open(filename,"rb"){|file| + file.pos=0 + fdw=fgetdw.call(file) + if fdw==0x46464952 # "RIFF" + filesize=fgetdw.call(file) + wave=fgetdw.call(file) + if wave!=0x45564157 # "WAVE" + return 2 + end + fmt=fgetdw.call(file) + if fmt!=0x20746d66 # "fmt " + return 2 + end + fmtsize=fgetdw.call(file) + format=fgetw.call(file) + if format!=1 + return 3 # unsupported + end + channels=fgetw.call(file) # channels (1 or 2) + if channels!=1 + return 3 # unsupported + end + rate=fgetdw.call(file) # samples per second + bytessec=fgetdw.call(file) # avg bytes per second + if bytessec==0 + return 2 + end + bytessample=fgetw.call(file) # bytes per sample + bitssample=fgetw.call(file) # bits per sample (8, 16, etc.) + if bitssample!=8 && bitssample!=16 + return 3 # unsupported + end + data=fgetdw.call(file) + if data!=0x61746164 # "data" + return 2 + end + datasize=fgetdw.call(file) + data=file.read(datasize) + samples=nil + if bitssample==8 + samples=data.unpack("C*") + start=0 + for i in 0...samples.length + s=samples[i] + if s<0x70 || s>=0x90 + start=i + break + end + end + finish=start + i=samples.length-1 + while i>=start + s=samples[i] + if s<0x70 || s>=0x90 + finish=i+1 + break + end + i-=1 + end + if finish==start + return 4 # Nothing was recorded + end + start=0 + finish=samples.length + wave=WaveData.new(rate,samples[start,finish-start]) + return wave + elsif bitssample==16 + samples=data.unpack("v*") + start=0 + for i in 0...samples.length + s=samples[i] + if s>0x1000 && s<0xF000 + start=i + break + end + end + finish=start + i=samples.length-1 + while i>=start + s=samples[i] + if s<0x1000 && s<0xF000 + finish=i+1 + break + end + i-=1 + end + if finish==start + return 4 # Nothing was recorded + end + start=0 + # Convert to 8-bit samples + for i in start...finish + samples[i]=((samples[i]-0x8000)>>8)&0xFF + end + finish=samples.length + return WaveData.new(rate,samples[start,finish-start]) + end + end + } + return 2 +end + +############################### + +begin + MciSendString = Win32API.new('winmm','mciSendString','%w(p,p,l,l)','l') + MciErrorString = Win32API.new('winmm','mciGetErrorString','%w(l,p,l)','l') +rescue + MciSendString=nil + MciErrorString=nil +end + +# Starts recording. Returns 0 if successful. +def beginRecord + return 256+72 if !MciSendString + MciSendString.call("open new type waveaudio alias RECORDER buffer 4",0,0,0) + MciSendString.call("set RECORDER channels 1",0,0,0) + retval=MciSendString.call("record RECORDER",0,0,0) + if retval!=0 + MciSendString.call("close RECORDER",0,0,0) + end + return retval +end + + +# Gets a single sample from the microphone. +# The beginRecord or beginRecordUI function must have been called beforehand. +def getRecorderSample + return 0x8000 if !MciSendString + buffer="\0"*256 + ret=0 + MciSendString.call("stop RECORDER",0,0,0) + MciSendString.call("status RECORDER bitspersample",buffer,256,0) + bitspersample=buffer.to_i + MciSendString.call("status RECORDER level",buffer,256,0) + MciSendString.call("record RECORDER",0,0,0) + if bitspersample==8 + ret=buffer.to_i<<8 # max 128 + else + ret=buffer.to_i # max 0x8000 + end + return ret +end + +def stopRecord() + return if !MciSendString + MciSendString.call("stop RECORDER",0,0,0) + MciSendString.call("close RECORDER",0,0,0) +end + +def endRecord(file) + return if !MciSendString + MciSendString.call("stop RECORDER",0,0,0) + if file && file!="" + MciSendString.call("save RECORDER #{file}",0,0,0) + end + MciSendString.call("close RECORDER",0,0,0) +end + +#Audio.sine(140,SoundEnvelope.initial(6400).change(140,11400),50) diff --git a/Kawariki-patches/ports/dummyPSystem_Utilities.rb b/Kawariki-patches/ports/dummyPSystem_Utilities.rb new file mode 100644 index 0000000..c1f1bcf --- /dev/null +++ b/Kawariki-patches/ports/dummyPSystem_Utilities.rb @@ -0,0 +1,2510 @@ +################################################################################ +# General purpose utilities +################################################################################ +def _pbNextComb(comb,length) + i=comb.length-1 + begin + valid=true + for j in i...comb.length + if j==i + comb[j]+=1 + else + comb[j]=comb[i]+(j-i) + end + if comb[j]>=length + valid=false + break + end + end + return true if valid + i-=1 + end while i>=0 + return false +end + +# Iterates through the array and yields each combination of _num_ elements in +# the array. +def pbEachCombination(array,num) + return if array.length> 16) + + ((((@seed & 0xffff0000) >> 16) * (@s1 & 0x0000ffff)) & 0x0000ffff) + + (((@seed & 0x0000ffff) * ((@s1 & 0xffff0000) >> 16)) & 0x0000ffff)) & + 0x0000ffff) << 16)) + @s2 + r=(@seed>>16) + r=(r+0xFFFFFFFF)+1 if r<0 + return r + end + + def getNext + r=(getNext16()<<16)|(getNext16()) + r=(r+0xFFFFFFFF)+1 if r<0 + return r + end +end + + + +################################################################################ +# JavaScript-related utilities +################################################################################ +# Returns true if the given string represents a valid object in JavaScript +# Object Notation, and false otherwise. + + + + +def pbIsJsonString(str) + + return false if !str || str.strip.empty? + + # Regular expressions for matching parts of a JSON string + d = /(?:^|:|,)(?: ?\[)+/ + charEscapes = /\\[\"\\\/nrtubf]/ + + # Match string literals that may contain escaped characters + # This will match strings with valid JSON escapes, such as \", \\, \n, \r, etc. + stringLiterals = /"(?:[^"\\\n\r\x00-\x1f\x7f\x80-\x9f\\]|\\.)*"/ + + # White space matching + whiteSpace = /[\s]+/ + + # Pre-process the string to replace escapes, literals, and whitespace + str = str.gsub(charEscapes, "@").gsub(stringLiterals, "true").gsub(whiteSpace, " ") + + # Prevent cases like "truetrue", "true true", "true[true]", or other malformed patterns + otherLiterals = /(true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)(?! ?[0-9a-z\-\[\{\"])/ + + str = str.gsub(otherLiterals, "]").gsub(d, "") + + # Check if the string matches the format of a valid JSON-like structure + return str =~ /^[\],:{}\s]*$/ ? true : false + +end + + + +# Returns a Ruby object that corresponds to the given string, which is encoded in +# JavaScript Object Notation (JSON). Returns nil if the string is not valid JSON. +def pbParseJson(str) + if !pbIsJsonString(str) + return nil + end + stringRE=/(\"(\\[\"\'\\rntbf]|\\u[0-9A-Fa-f]{4,4}|[^\\\"])*\")/ #" + strings=[] + str=str.gsub(stringRE){ + sl=strings.length + ss=$1 + if ss.include?("\\u") + ss.gsub!(/\\u([0-9A-Fa-f]{4,4})/){ + codepoint=$1.to_i(16) + if codepoint<=0x7F + next sprintf("\\x%02X",codepoint) + elsif codepoint<=0x7FF + next sprintf("%s%s", + (0xC0|((codepoint>>6)&0x1F)).chr, + (0x80|(codepoint &0x3F)).chr) + else + next sprintf("%s%s%s", + (0xE0|((codepoint>>12)&0x0F)).chr, + (0x80|((codepoint>>6)&0x3F)).chr, + (0x80|(codepoint &0x3F)).chr) + end + } + end + strings.push(eval(ss)) + next sprintf("strings[%d]",sl) + } + str=str.gsub(/\:/,"=>") + str=str.gsub(/null/,"nil") + return eval("("+str+")") +end + + + +################################################################################ +# XML-related utilities +################################################################################ +# Represents XML content. +class MiniXmlContent + attr_reader :value + + def initialize(value) + @value=value + end +end + + + +# Represents an XML element. +class MiniXmlElement + attr_accessor :name,:attributes,:children + + def initialize(name) + @name=name + @attributes={} + @children=[] + end + +# Gets the value of the attribute with the given name, or nil if it doesn't +# exist. + def a(name) + self.attributes[name] + end + +# Gets the entire text of this element. + def value + ret="" + for c in @children + ret+=c.value + end + return ret + end + +# Gets the first child of this element with the given name, or nil if it +# doesn't exist. + def e(name) + for c in @children + return c if c.is_a?(MiniXmlElement) && c.name==name + end + return nil + end + + def eachElementNamed(name) + for c in @children + yield c if c.is_a?(MiniXmlElement) && c.name==name + end + end +end + + + +# A small class for reading simple XML documents. Such documents must +# meet the following restrictions: +# They may contain comments and processing instructions, but they are +# ignored. +# They can't contain any entity references other than 'gt', 'lt', +# 'amp', 'apos', or 'quot'. +# They can't contain a DOCTYPE declaration or DTDs. +class MiniXmlReader + def initialize(data) + @root=nil + @elements=[] + @done=false + @data=data + @content="" + end + + def createUtf8(codepoint) #:nodoc: + raise ArgumentError.new("Illegal character") if codepoint<9 || + codepoint==11||codepoint==12||(codepoint>=14 && codepoint<32) || + codepoint==0xFFFE||codepoint==0xFFFF||(codepoint>=0xD800 && codepoint<0xE000) + if codepoint<=0x7F + return codepoint.chr + elsif codepoint<=0x7FF + str=(0xC0|((codepoint>>6)&0x1F)).chr + str+=(0x80|(codepoint &0x3F)).chr + return str + elsif codepoint<=0xFFFF + str=(0xE0|((codepoint>>12)&0x0F)).chr + str+=(0x80|((codepoint>>6)&0x3F)).chr + str+=(0x80|(codepoint &0x3F)).chr + return str + elsif codepoint<=0x10FFFF + str=(0xF0|((codepoint>>18)&0x07)).chr + str+=(0x80|((codepoint>>12)&0x3F)).chr + str+=(0x80|((codepoint>>6)&0x3F)).chr + str+=(0x80|(codepoint &0x3F)).chr + return str + else + raise ArgumentError.new("Illegal character") + end + return str + end + + def unescape(attr) #:nodoc: + attr=attr.gsub(/\r(\n|$|(?=[^\n]))/,"\n") + raise ArgumentError.new("Attribute value contains '<'") if attr.include?("<") + attr=attr.gsub(/&(lt|gt|apos|quot|amp|\#([0-9]+)|\#x([0-9a-fA-F]+));|([\n\r\t])/){ + next " " if $4=="\n"||$4=="\r"||$4=="\t" + next "<" if $1=="lt" + next ">" if $1=="gt" + next "'" if $1=="apos" + next "\"" if $1=="quot" + next "&" if $1=="amp" + next createUtf8($2.to_i) if $2 + next createUtf8($3.to_i(16)) if $3 + } + return attr + end + + def readAttributes(attribs) #:nodoc: + ret={} + while attribs.length>0 + if attribs[/(\s+([\w\-]+)\s*\=\s*\"([^\"]*)\")/] + attribs=attribs[$1.length,attribs.length] + name=$2; value=$3 + if ret[name]!=nil + raise ArgumentError.new("Attribute already exists") + end + ret[name]=unescape(value) + elsif attribs[/(\s+([\w\-]+)\s*\=\s*\'([^\']*)\')/] + attribs=attribs[$1.length,attribs.length] + name=$2; value=$3 + if ret[name]!=nil + raise ArgumentError.new("Attribute already exists") + end + ret[name]=unescape(value) + else + raise ArgumentError.new("Can't parse attributes") + end + end + return ret + end + +# Reads the entire contents of an XML document. Returns the root element of +# the document or raises an ArgumentError if an error occurs. + def read + if @data[/\A((\xef\xbb\xbf)?<\?xml\s+version\s*=\s*(\"1\.[0-9]\"|\'1\.[0-9]\')(\s+encoding\s*=\s*(\"[^\"]*\"|\'[^\']*\'))?(\s+standalone\s*=\s*(\"(yes|no)\"|\'(yes|no)\'))?\s*\?>)/] + # Ignore XML declaration + @data=@data[$1.length,@data.length] + end + while readOneElement(); end + return @root + end + + def readOneElement #:nodoc: + if @data[/\A\s*\z/] + @data="" + if !@root + raise ArgumentError.new("Not an XML document.") + elsif !@done + raise ArgumentError.new("Unexpected end of document.") + end + return false + end + if @data[/\A(\s*<([\w\-]+)((?:\s+[\w\-]+\s*\=\s*(?:\"[^\"]*\"|\'[^\']*\'))*)\s*(\/>|>))/] + @data=@data[$1.length,@data.length] + elementName=$2 + attributes=$3 + endtag=$4 + if @done + raise ArgumentError.new("Element tag at end of document") + end + if @content.length>0 && @elements.length>0 + @elements[@elements.length-1].children.push(MiniXmlContent.new(@content)) + @content="" + end + element=MiniXmlElement.new(elementName) + element.attributes=readAttributes(attributes) + if !@root + @root=element + else + @elements[@elements.length-1].children.push(element) + end + if endtag==">" + @elements.push(element) + else + if @elements.length==0 + @done=true + end + end + elsif @data[/\A()/] + # ignore comments + if $2.include?("--") + raise ArgumentError.new("Incorrect comment") + end + @data=@data[$1.length,@data.length] + elsif @data[/\A(<\?([\w\-]+)\s+[\s\S]*?\?>)/] + # ignore processing instructions + @data=@data[$1.length,@data.length] + if $2.downcase=="xml" + raise ArgumentError.new("'xml' processing instruction not allowed") + end + elsif @data[/\A(<\?([\w\-]+)\?>)/] + # ignore processing instructions + @data=@data[$1.length,@data.length] + if $2.downcase=="xml" + raise ArgumentError.new("'xml' processing instruction not allowed") + end + elsif @data[/\A(\s*<\/([\w\-]+)>)/] + @data=@data[$1.length,@data.length] + elementName=$2 + if @done + raise ArgumentError.new("End tag at end of document") + end + if @elements.length==0 + raise ArgumentError.new("Unexpected end tag") + elsif @elements[@elements.length-1].name!=elementName + raise ArgumentError.new("Incorrect end tag") + else + if @content.length>0 + @elements[@elements.length-1].children.push(MiniXmlContent.new(@content)) + @content="" + end + @elements.pop() + if @elements.length==0 + @done=true + end + end + else + if @elements.length>0 + # Parse content + if @data[/\A([^<&]+)/] + content=$1 + @data=@data[content.length,@data.length] + if content.include?("]]>") + raise ArgumentError.new("Incorrect content") + end + content.gsub!(/\r(\n|\z|(?=[^\n]))/,"\n") + @content+=content + elsif @data[/\A(<\!\[CDATA\[([\s\S]*?)\]\]>)/] + content=$2 + @data=@data[$1.length,@data.length] + content.gsub!(/\r(\n|\z|(?=[^\n]))/,"\n") + @content+=content + elsif @data[/\A(&(lt|gt|apos|quot|amp|\#([0-9]+)|\#x([0-9a-fA-F]+));)/] + @data=@data[$1.length,@data.length] + content="" + if $2=="lt"; content="<" + elsif $2=="gt"; content=">" + elsif $2=="apos"; content="'" + elsif $2=="quot"; content="\"" + elsif $2=="amp"; content="&" + elsif $3; content=createUtf8($2.to_i) + elsif $4; content=createUtf8($3.to_i(16)) + end + @content+=content + elsif !@data[/\A=8 + meta=pbGetMetadata(0,MetadataPlayerA+id) + return false if !meta + $Trainer.trainertype=meta[0] if $Trainer + $game_player.character_name=meta[1] + $game_player.character_hue=0 + $PokemonGlobal.playerID=id + $Trainer.metaID=id if $Trainer +end + +def pbGetPlayerGraphic + id=$PokemonGlobal.playerID + return "" if id<0 || id>=8 + meta=pbGetMetadata(0,MetadataPlayerA+id) + return "" if !meta + return pbPlayerSpriteFile(meta[0]) +end + +def pbGetPlayerTrainerType + id=$PokemonGlobal.playerID + return 0 if id<0 || id>=8 + meta=pbGetMetadata(0,MetadataPlayerA+id) + return 0 if !meta + return meta[0] +end + +def pbGetTrainerTypeGender(trainertype) + ret=2 # 2 = gender unknown + pbRgssOpen("Data/trainertypes.dat","rb"){|f| + trainertypes=Marshal.load(f) + if !trainertypes[trainertype] + ret=2 + else + ret=trainertypes[trainertype][7] + ret=2 if !ret + end + } + return ret +end + +def pbTrainerName(name=nil,outfit=0) + if $PokemonGlobal.playerID<0 + pbChangePlayer(0) + end + trainertype=pbGetPlayerTrainerType + trname=name + $Trainer=PokeBattle_Trainer.new(trname,trainertype) + $Trainer.outfit=outfit + if trname==nil + trname=pbEnterPlayerName(_INTL("Your name?"),0,7) + if trname=="" +# gender=pbGetTrainerTypeGender(trainertype) +# trname=pbSuggestTrainerName(gender) + #REDMAGE - replaced the above two lines with the one below + trname=[_INTL("Simon"),_INTL("Sofia")][$PokemonGlobal.playerID] + end + end + $Trainer.name=trname + $PokemonBag=PokemonBag.new + $PokemonTemp.begunNewGame=true +end + +def pbSuggestTrainerName(gender) + userName=pbGetUserName() + userName=userName.gsub(/\s+.*$/,"") + if userName.length>0 && userName.length<7 + userName[0,1]=userName[0,1].upcase + return userName + end + userName=userName.gsub(/\d+$/,"") + if userName.length>0 && userName.length<7 + userName[0,1]=userName[0,1].upcase + return userName + end + owner=MiniRegistry.get(MiniRegistry::HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "RegisteredOwner","") + owner=owner.gsub(/\s+.*$/,"") + if owner.length>0 && owner.length<7 + owner[0,1]=owner[0,1].upcase + return owner + end + return getRandomNameEx(gender,nil,1,7) +end + +def pbGetUserName() + buffersize=100 + getUserName=Win32API.new('advapi32.dll','GetUserName','pp','i') + 10.times do + size=[buffersize].pack("V") + buffer="\0"*buffersize + if getUserName.call(buffer,size)!=0 + return buffer.gsub(/\0/,"") + end + buffersize+=200 + end + return "" +end + +def getRandomNameEx(type,variable,upper,maxLength=100) + return "" if maxLength<=0 + name="" + 50.times { + name="" + formats=[] + case type + when 0 # Names for males + formats=%w( F5 BvE FE FE5 FEvE ) + when 1 # Names for females + formats=%w( vE6 vEvE6 BvE6 B4 v3 vEv3 Bv3 ) + when 2 # Neutral gender names + formats=%w( WE WEU WEvE BvE BvEU BvEvE ) + else + return "" + end + format=formats[rand(formats.length)] + format.scan(/./) {|c| + case c + when "c" # consonant + set=%w( b c d f g h j k l m n p r s t v w x z ) + name+=set[rand(set.length)] + when "v" # vowel + set=%w( a a a e e e i i i o o o u u u ) + name+=set[rand(set.length)] + when "W" # beginning vowel + set=%w( a a a e e e i i i o o o u u u au au ay ay + ea ea ee ee oo oo ou ou ) + name+=set[rand(set.length)] + when "U" # ending vowel + set=%w( a a a a a e e e i i i o o o o o u u ay ay ie ie ee ue oo ) + name+=set[rand(set.length)] + when "B" # beginning consonant + set1=%w( b c d f g h j k l l m n n p r r s s t t v w y z ) + set2=%w( + bl br ch cl cr dr fr fl gl gr kh kl kr ph pl pr sc sk sl + sm sn sp st sw th tr tw vl zh ) + name+=rand(3)>0 ? set1[rand(set1.length)] : set2[rand(set2.length)] + when "E" # ending consonant + set1=%w( b c d f g h j k k l l m n n p r r s s t t v z ) + set2=%w( bb bs ch cs ds fs ft gs gg ld ls + nd ng nk rn kt ks + ms ns ph pt ps sk sh sp ss st rd + rn rp rm rt rk ns th zh) + name+=rand(3)>0 ? set1[rand(set1.length)] : set2[rand(set2.length)] + when "f" # consonant and vowel + set=%w( iz us or ) + name+=set[rand(set.length)] + when "F" # consonant and vowel + set=%w( bo ba be bu re ro si mi zho se nya gru gruu glee gra glo ra do zo ri + di ze go ga pree pro po pa ka ki ku de da ma mo le la li ) + name+=set[rand(set.length)] + when "2" + set=%w( c f g k l p r s t ) + name+=set[rand(set.length)] + when "3" + set=%w( nka nda la li ndra sta cha chie ) + name+=set[rand(set.length)] + when "4" + set=%w( una ona ina ita ila ala ana ia iana ) + name+=set[rand(set.length)] + when "5" + set=%w( e e o o ius io u u ito io ius us ) + name+=set[rand(set.length)] + when "6" + set=%w( a a a elle ine ika ina ita ila ala ana ) + name+=set[rand(set.length)] + end + } + break if name.length<=maxLength + } + name=name[0,maxLength] + case upper + when 0 + name=name.upcase + when 1 + name[0,1]=name[0,1].upcase + end + if $game_variables && variable + $game_variables[variable]=name + $game_map.need_refresh = true if $game_map + end + return name +end + +def getRandomName(maxLength=100) + return getRandomNameEx(2,nil,nil,maxLength) +end + + + +################################################################################ +# Event timing utilities +################################################################################ +def pbTimeEvent(variableNumber,secs=86400) + if variableNumber && variableNumber>=0 + if $game_variables + secs=0 if secs<0 + timenow=pbGetTimeNow + $game_variables[variableNumber]=[timenow.to_f,secs] + $game_map.refresh if $game_map + end + end +end + +def pbTimeEventDays(variableNumber,days=0) + if variableNumber && variableNumber>=0 + if $game_variables + days=0 if days<0 + timenow=pbGetTimeNow + time=timenow.to_f + expiry=(time%86400.0)+(days*86400.0) + $game_variables[variableNumber]=[time,expiry-time] + $game_map.refresh if $game_map + end + end +end + +def pbTimeEventValid(variableNumber) + retval=false + if variableNumber && variableNumber>=0 && $game_variables + value=$game_variables[variableNumber] + if value.is_a?(Array) + timenow=pbGetTimeNow + retval=(timenow.to_f - value[0] > value[1]) # value[1] is age in seconds + retval=false if value[1]<=0 # zero age + end + if !retval + $game_variables[variableNumber]=0 + $game_map.refresh if $game_map + end + end + return retval +end + + + +################################################################################ +# Constants utilities +################################################################################ +def isConst?(val,mod,constant) + begin + isdef=mod.const_defined?(constant.to_sym) + return false if !isdef + rescue + return false + end + return (val==mod.const_get(constant.to_sym)) +end + +def hasConst?(mod,constant) + return false if !mod || !constant || constant=="" + return mod.const_defined?(constant.to_sym) rescue false +end + +def getConst(mod,constant) + return nil if !mod || !constant || constant=="" + return mod.const_get(constant.to_sym) rescue nil +end + +def getID(mod,constant) + return nil if !mod || !constant || constant=="" + if constant.is_a?(Symbol) || constant.is_a?(String) + if (mod.const_defined?(constant.to_sym) rescue false) + return mod.const_get(constant.to_sym) rescue 0 + else + return 0 + end + else + return constant + end +end + + + +################################################################################ +# Implements methods that act on arrays of items. Each element in an item +# array is itself an array of [itemID, itemCount]. +# Used by the Bag, PC item storage, and Triple Triad. +################################################################################ +module ItemStorageHelper + # Returns the quantity of the given item in the items array, maximum size per slot, and item ID + def self.pbQuantity(items,maxsize,item) + ret=0 + for i in 0...maxsize + itemslot=items[i] + if itemslot && itemslot[0]==item + ret+=itemslot[1] + end + end + return ret + end + + # Deletes an item from items array, maximum size per slot, item, and number of items to delete + def self.pbDeleteItem(items,maxsize,item,qty) + raise "Invalid value for qty: #{qty}" if qty<0 + return true if qty==0 + ret=false + for i in 0...maxsize + itemslot=items[i] + if itemslot && itemslot[0]==item + amount=[qty,itemslot[1]].min + itemslot[1]-=amount + qty-=amount + items[i]=nil if itemslot[1]==0 + if qty==0 + ret=true + break + end + end + end + items.compact! + return ret + end + + def self.pbCanStore?(items,maxsize,maxPerSlot,item,qty) + raise "Invalid value for qty: #{qty}" if qty<0 + return true if qty==0 + for i in 0...maxsize + itemslot=items[i] + if !itemslot + qty-=[qty,maxPerSlot].min + return true if qty==0 + elsif itemslot[0]==item && itemslot[1]=0 + $game_variables[id]=value if $game_variables + $game_map.need_refresh = true if $game_map + end +end + +# Runs a common event and waits until the common event is finished. +# Requires the script "PokemonMessages" +def pbCommonEvent(id) + return false if id<0 + ce=$data_common_events[id] + return false if !ce + celist=ce.list + interp=Interpreter.new + interp.setup(celist,0) + begin + Graphics.update + Input.update + interp.update + pbUpdateSceneMap + end while interp.running? + return true +end + +def pbExclaim(event,id=EXCLAMATION_ANIMATION_ID,tinting=false) + if event.is_a?(Array) + sprite=nil + done=[] + for i in event + if !done.include?(i.id) + sprite=$scene.spriteset.addUserAnimation(id,i.x,i.y,tinting) + done.push(i.id) + end + end + else + sprite=$scene.spriteset.addUserAnimation(id,event.x,event.y-1,tinting) #Nave exclamaition mark fix (added that -1) + end + while !sprite.disposed? + Graphics.update + Input.update + pbUpdateSceneMap + end +end + +def pbNoticePlayer(event) + if !pbFacingEachOther(event,$game_player) || !Input.press?(Input::C) + pbExclaim(event) + end + pbTurnTowardEvent($game_player,event) + Kernel.pbMoveTowardPlayer(event) +end + + + +################################################################################ +# Loads Pokémon/item/trainer graphics +################################################################################ +def pbPokemonBitmapFile(species, shiny, back=false) # Unused + if shiny + # Load shiny bitmap + ret=sprintf("Graphics/Battlers/%ss%s",getConstantName(PBSpecies,species),back ? "b" : "") rescue nil + if !pbResolveBitmap(ret) + ret=sprintf("Graphics/Battlers/%03ds%s",species,back ? "b" : "") + end + return ret + else + # Load normal bitmap + ret=sprintf("Graphics/Battlers/%s%s",getConstantName(PBSpecies,species),back ? "b" : "") rescue nil + if !pbResolveBitmap(ret) + ret=sprintf("Graphics/Battlers/%03d%s",species,back ? "b" : "") + end + return ret + end +end + +def pbLoadPokemonBitmap(pokemon, back=false) + return pbLoadPokemonBitmapSpecies(pokemon,pokemon.species,back) +end + +# Note: Returns an AnimatedBitmap, not a Bitmap +def pbLoadPokemonBitmapSpecies(pokemon, species, back=false) + ret=nil + if pokemon.isEgg? + bitmapFileName=sprintf("Graphics/Battlers/%segg",getConstantName(PBSpecies,species)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Battlers/%03degg",species) + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Battlers/egg") + end + end + bitmapFileName=pbResolveBitmap(bitmapFileName) + else + bitmapFileName=pbCheckPokemonBitmapFiles([species,back, + (pokemon.isFemale?), + pokemon.isShiny?, + (pokemon.form rescue 0), + (pokemon.isShadow? rescue false)]) + # Alter bitmap if supported + alterBitmap=(MultipleForms.getFunction(species,"alterBitmap") rescue nil) + end + if bitmapFileName && alterBitmap + animatedBitmap=AnimatedBitmap.new(bitmapFileName) + copiedBitmap=animatedBitmap.copy + animatedBitmap.dispose + copiedBitmap.each {|bitmap| + alterBitmap.call(pokemon,bitmap) + } + ret=copiedBitmap + elsif bitmapFileName + ret=AnimatedBitmap.new(bitmapFileName) + end + return ret +end + +# Note: Returns an AnimatedBitmap, not a Bitmap +def pbLoadSpeciesBitmap(species,female=false,form=0,shiny=false,shadow=false,back=false,egg=false) + ret=nil + if egg + bitmapFileName=sprintf("Graphics/Battlers/%segg",getConstantName(PBSpecies,species)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Battlers/%03degg",species) + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Battlers/egg") + end + end + bitmapFileName=pbResolveBitmap(bitmapFileName) + else + bitmapFileName=pbCheckPokemonBitmapFiles([species,back,female,shiny,form,shadow]) + end + if bitmapFileName + ret=AnimatedBitmap.new(bitmapFileName) + end + return ret +end + +def pbCheckPokemonBitmapFiles(params) + species=params[0] + back=params[1] + factors=[] + factors.push([5,params[5],false]) if params[5] && params[5]!=false # shadow + factors.push([2,params[2],false]) if params[2] && params[2]!=false # gender + factors.push([3,params[3],false]) if params[3] && params[3]!=false # shiny + factors.push([4,params[4].to_s,""]) if params[4] && params[4].to_s!="" && + params[4].to_s!="0" # form + tshadow=false + tgender=false + tshiny=false + tform="" + for i in 0...2**factors.length + for j in 0...factors.length + case factors[j][0] + when 2 # gender + tgender=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + when 3 # shiny + tshiny=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + when 4 # form + tform=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + when 5 # shadow + tshadow=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + end + end + bitmapFileName=sprintf("Graphics/Battlers/%s%s%s%s%s%s", + getConstantName(PBSpecies,species), + tgender ? "f" : "", + tshiny ? "s" : "", + back ? "b" : "", + (tform!="" ? "_"+tform : ""), + tshadow ? "_shadow" : "") rescue nil + ret=pbResolveBitmap(bitmapFileName) + return ret if ret + bitmapFileName=sprintf("Graphics/Battlers/%03d%s%s%s%s%s", + species, + tgender ? "f" : "", + tshiny ? "s" : "", + back ? "b" : "", + (tform!="" ? "_"+tform : ""), + tshadow ? "_shadow" : "") + ret=pbResolveBitmap(bitmapFileName) + return ret if ret + end + return nil +end + +def pbLoadPokemonIcon(pokemon) + return AnimatedBitmap.new(pbPokemonIconFile(pokemon)).deanimate +end + +def pbPokemonIconFile(pokemon) + bitmapFileName=nil + bitmapFileName=pbCheckPokemonIconFiles([pokemon.species, + (pokemon.isFemale?), + pokemon.isShiny?, + (pokemon.form rescue 0), + (pokemon.isShadow? rescue false)], + pokemon.isEgg?) + return bitmapFileName +end + +def pbCheckPokemonIconFiles(params,egg=false) + species=params[0] + if egg + bitmapFileName=sprintf("Graphics/Icons/icon%segg",getConstantName(PBSpecies,species)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Icons/icon%03degg",species) + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Icons/iconEgg") + end + end + return pbResolveBitmap(bitmapFileName) + else + factors=[] + factors.push([4,params[4],false]) if params[4] && params[4]!=false # shadow + factors.push([1,params[1],false]) if params[1] && params[1]!=false # gender + factors.push([2,params[2],false]) if params[2] && params[2]!=false # shiny + factors.push([3,params[3].to_s,""]) if params[3] && params[3].to_s!="" && + params[3].to_s!="0" # form + tshadow=false + tgender=false + tshiny=false + tform="" + for i in 0...2**factors.length + for j in 0...factors.length + case factors[j][0] + when 1 # gender + tgender=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + when 2 # shiny + tshiny=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + when 3 # form + tform=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + when 4 # shadow + tshadow=((i/(2**j))%2==0) ? factors[j][1] : factors[j][2] + end + end + bitmapFileName=sprintf("Graphics/Icons/icon%s%s%s%s%s", + getConstantName(PBSpecies,species), + tgender ? "f" : "", + tshiny ? "s" : "", + (tform!="" ? "_"+tform : ""), + tshadow ? "_shadow" : "") rescue nil + ret=pbResolveBitmap(bitmapFileName) + return ret if ret + bitmapFileName=sprintf("Graphics/Icons/icon%03d%s%s%s%s", + species, + tgender ? "f" : "", + tshiny ? "s" : "", + (tform!="" ? "_"+tform : ""), + tshadow ? "_shadow" : "") + ret=pbResolveBitmap(bitmapFileName) + return ret if ret + end + end + return nil +end + +def pbPokemonFootprintFile(pokemon) # Used by the Pokédex + return nil if !pokemon + if pokemon.is_a?(Numeric) + bitmapFileName=sprintf("Graphics/Icons/Footprints/footprint%s",getConstantName(PBSpecies,pokemon)) rescue nil + bitmapFileName=sprintf("Graphics/Icons/Footprints/footprint%03d",pokemon) if !pbResolveBitmap(bitmapFileName) + else + bitmapFileName=sprintf("Graphics/Icons/Footprints/footprint%s_%d",getConstantName(PBSpecies,pokemon.species),(pokemon.form rescue 0)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Icons/Footprints/footprint%03d_%d",pokemon.species,(pokemon.form rescue 0)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Icons/Footprints/footprint%s",getConstantName(PBSpecies,pokemon.species)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Icons/Footprints/footprint%03d",pokemon.species) + end + end + end + end + return pbResolveBitmap(bitmapFileName) +end + +def pbItemIconFile(item) + return nil if !item + bitmapFileName=nil + if item==0 + bitmapFileName=sprintf("Graphics/Icons/itemBack") + else + bitmapFileName=sprintf("Graphics/Icons/item%s",getConstantName(PBItems,item)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Icons/item%03d",item) + end + end + return bitmapFileName +end + +def pbMailBackFile(item) + return nil if !item + bitmapFileName=sprintf("Graphics/Pictures/mail%s",getConstantName(PBItems,item)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Pictures/mail%03d",item) + end + return bitmapFileName +end + +def pbTrainerCharFile(type) + return nil if !type + bitmapFileName=sprintf("Graphics/Characters/trchar%s",getConstantName(PBTrainers,type)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Characters/trchar%03d",type) + end + return bitmapFileName +end + +def pbTrainerCharNameFile(type) + return nil if !type + bitmapFileName=sprintf("trchar%s",getConstantName(PBTrainers,type)) rescue nil + if !pbResolveBitmap(sprintf("Graphics/Characters/"+bitmapFileName)) + bitmapFileName=sprintf("trchar%03d",type) + end + return bitmapFileName +end + +def pbTrainerHeadFile(type) + return nil if !type + bitmapFileName=sprintf("Graphics/Pictures/mapPlayer%s",getConstantName(PBTrainers,type)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Pictures/mapPlayer%03d",type) + end + return bitmapFileName +end + +def pbPlayerHeadFile(type) + return nil if !type + outfit=$Trainer ? $Trainer.outfit : 0 + bitmapFileName=sprintf("Graphics/Pictures/mapPlayer%s_%d", + getConstantName(PBTrainers,type),outfit) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Pictures/mapPlayer%03d_%d",type,outfit) + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=pbTrainerHeadFile(type) + end + end + return bitmapFileName +end + +def pbTrainerSpriteFile(type) + return nil if !type + bitmapFileName=sprintf("Graphics/Characters/trainer%s",getConstantName(PBTrainers,type)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Characters/trainer%03d",type) + end + return bitmapFileName +end + +def pbTrainerSpriteBackFile(type) + return nil if !type + bitmapFileName=sprintf("Graphics/Characters/trback%s",getConstantName(PBTrainers,type)) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Characters/trback%03d",type) + end + return bitmapFileName +end + +def pbPlayerSpriteFile(type) + return nil if !type + outfit=$Trainer ? $Trainer.outfit : 0 + bitmapFileName=sprintf("Graphics/Characters/trainer%s_%d", + getConstantName(PBTrainers,type),outfit) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Characters/trainer%03d_%d",type,outfit) + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=pbTrainerSpriteFile(type) + end + end + return bitmapFileName +end + +def pbPlayerSpriteBackFile(type) + return nil if !type + outfit=$Trainer ? $Trainer.outfit : 0 + bitmapFileName=sprintf("Graphics/Characters/trback%s_%d", + getConstantName(PBTrainers,type),outfit) rescue nil + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=sprintf("Graphics/Characters/trback%03d_%d",type,outfit) + if !pbResolveBitmap(bitmapFileName) + bitmapFileName=pbTrainerSpriteBackFile(type) + end + end + return bitmapFileName +end + + + +################################################################################ +# Loads music and sound effects +################################################################################ +def pbResolveAudioSE(file) + return nil if !file + if RTP.exists?("Audio/SE/"+file,["",".wav",".mp3",".ogg"]) + return RTP.getPath("Audio/SE/"+file,["",".wav",".mp3",".ogg"]) + end + return nil +end + +def pbCryFrameLength(pokemon,pitch=nil) + return 0 if !pokemon + pitch=100 if !pitch + pitch=pitch.to_f/100 + return 0 if pitch<=0 + playtime=0.0 + if pokemon.is_a?(Numeric) + pkmnwav=pbResolveAudioSE(pbCryFile(pokemon)) + playtime=getPlayTime(pkmnwav) if pkmnwav + elsif !pokemon.isEgg? + if pokemon.respond_to?("chatter") && pokemon.chatter + playtime=pokemon.chatter.time + pitch=1.0 + else + pkmnwav=pbResolveAudioSE(pbCryFile(pokemon)) + playtime=getPlayTime(pkmnwav) if pkmnwav + end + end + playtime/=pitch # sound is lengthened the lower the pitch + # 4 is added to provide a buffer between sounds + return (playtime*Graphics.frame_rate).ceil+4 +end + +def pbPlayCry(pokemon,volume=90,pitch=nil) + return if !pokemon + if pokemon.is_a?(Numeric) + pkmnwav=pbCryFile(pokemon) + if pkmnwav + pbSEPlay(RPG::AudioFile.new(pkmnwav,volume,pitch ? pitch : 100)) rescue nil + end + elsif !pokemon.isEgg? + if pokemon.respond_to?("chatter") && pokemon.chatter + pokemon.chatter.play + else + pkmnwav=pbCryFile(pokemon) + if pkmnwav + pbSEPlay(RPG::AudioFile.new(pkmnwav,volume, + pitch ? pitch : (pokemon.hp*25/pokemon.totalhp)+75)) rescue nil + end + end + end +end + +def pbCryFile(pokemon) + return nil if !pokemon + if pokemon.is_a?(Numeric) + filename=sprintf("Cries/%sCry",getConstantName(PBSpecies,pokemon)) rescue nil + filename=sprintf("Cries/%03dCry",pokemon) if !pbResolveAudioSE(filename) + return filename if pbResolveAudioSE(filename) + elsif !pokemon.isEgg? + filename=sprintf("Cries/%sCry_%d",getConstantName(PBSpecies,pokemon.species),(pokemon.form rescue 0)) rescue nil + filename=sprintf("Cries/%03dCry_%d",pokemon.species,(pokemon.form rescue 0)) if !pbResolveAudioSE(filename) + if !pbResolveAudioSE(filename) + filename=sprintf("Cries/%sCry",getConstantName(PBSpecies,pokemon.species)) rescue nil + end + filename=sprintf("Cries/%03dCry",pokemon.species) if !pbResolveAudioSE(filename) + return filename if pbResolveAudioSE(filename) + end + return nil +end + +def pbGetWildBattleBGM(species) + if $PokemonGlobal.nextBattleBGM + return $PokemonGlobal.nextBattleBGM.clone + end + ret=nil + if !ret && $game_map + # Check map-specific metadata + music=pbGetMetadata($game_map.map_id,MetadataMapWildBattleBGM) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + if !ret + # Check global metadata + music=pbGetMetadata(0,MetadataWildBattleBGM) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + ret=pbStringToAudioFile("BT-Wild.mp3") if !ret + return ret +end + +def pbGetWildVictoryME + if $PokemonGlobal.nextBattleME + return $PokemonGlobal.nextBattleME.clone + end + ret=nil + if !ret && $game_map + # Check map-specific metadata + music=pbGetMetadata($game_map.map_id,MetadataMapWildVictoryME) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + if !ret + # Check global metadata + music=pbGetMetadata(0,MetadataWildVictoryME) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + ret=pbStringToAudioFile("001-Victory01") if !ret + ret.name="../../Audio/ME/"+ret.name + return ret +end + +def pbPlayTrainerIntroME(trainertype) + pbRgssOpen("Data/trainertypes.dat","rb"){|f| + trainertypes=Marshal.load(f) + if trainertypes[trainertype] + bgm=trainertypes[trainertype][6] + if bgm && bgm!="" + bgm=pbStringToAudioFile(bgm) + pbMEPlay(bgm) + return + end + end + } +end + +def pbGetTrainerBattleBGM(trainer) # can be a PokeBattle_Trainer or an array of PokeBattle_Trainer + if $PokemonGlobal.nextBattleBGM + return $PokemonGlobal.nextBattleBGM.clone + end + music=nil + pbRgssOpen("Data/trainertypes.dat","rb"){|f| + trainertypes=Marshal.load(f) + if !trainer.is_a?(Array) + trainerarray=[trainer] + else + trainerarray=trainer + end + for i in 0...trainerarray.length + trainertype=trainerarray[i].trainertype + if trainertypes[trainertype] + music=trainertypes[trainertype][4] + end + end + } + ret=nil + if music && music!="" + ret=pbStringToAudioFile(music) + end + if !ret && $game_map + # Check map-specific metadata + music=pbGetMetadata($game_map.map_id,MetadataMapTrainerBattleBGM) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + if !ret + # Check global metadata + music=pbGetMetadata(0,MetadataTrainerBattleBGM) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + ret=pbStringToAudioFile("005-Boss01") if !ret + return ret +end + +def pbGetTrainerBattleBGMFromType(trainertype) + if $PokemonGlobal.nextBattleBGM + return $PokemonGlobal.nextBattleBGM.clone + end + music=nil + pbRgssOpen("Data/trainertypes.dat","rb"){|f| + trainertypes=Marshal.load(f) + if trainertypes[trainertype] + music=trainertypes[trainertype][4] + end + } + ret=nil + if music && music!="" + ret=pbStringToAudioFile(music) + end + if !ret && $game_map + # Check map-specific metadata + music=pbGetMetadata($game_map.map_id,MetadataMapTrainerBattleBGM) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + if !ret + # Check global metadata + music=pbGetMetadata(0,MetadataTrainerBattleBGM) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + ret=pbStringToAudioFile("005-Boss01") if !ret + return ret +end + +def pbGetTrainerVictoryME(trainer) # can be a PokeBattle_Trainer or an array of PokeBattle_Trainer + if $PokemonGlobal.nextBattleME + return $PokemonGlobal.nextBattleME.clone + end + music=nil + pbRgssOpen("Data/trainertypes.dat","rb"){|f| + trainertypes=Marshal.load(f) + if !trainer.is_a?(Array) + trainerarray=[trainer] + else + trainerarray=trainer + end + for i in 0...trainerarray.length + trainertype=trainerarray[i].trainertype + if trainertypes[trainertype] + music=trainertypes[trainertype][5] + end + end + } + ret=nil + if music && music!="" + ret=pbStringToAudioFile(music) + end + if !ret && $game_map + # Check map-specific metadata + music=pbGetMetadata($game_map.map_id,MetadataMapTrainerVictoryME) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + if !ret + # Check global metadata + music=pbGetMetadata(0,MetadataTrainerVictoryME) + if music && music!="" + ret=pbStringToAudioFile(music) + end + end + ret=pbStringToAudioFile("001-Victory01") if !ret + ret.name="../../Audio/ME/"+ret.name + return ret +end + + + +################################################################################ +# Creating and storing Pokémon +################################################################################ +# For demonstration purposes only, not to be used in a real game. +def pbCreatePokemon + party=[] + species=[:PIKACHU,:PIDGEOTTO,:KADABRA,:GYARADOS,:DIGLETT,:CHANSEY] + for id in species + party.push(getConst(PBSpecies,id)) if hasConst?(PBSpecies,id) + end + # Species IDs of the Pokémon to be created + for i in 0...party.length + species=party[i] + # Generate Pokémon with species and level 20 + $Trainer.party[i]=PokeBattle_Pokemon.new(species,20,$Trainer) + $Trainer.seen[species]=true # Set this species to seen and owned + $Trainer.owned[species]=true + pbSeenForm($Trainer.party[i]) + end + $Trainer.party[1].pbLearnMove(:FLY) + $Trainer.party[2].pbLearnMove(:FLASH) + $Trainer.party[2].pbLearnMove(:TELEPORT) + $Trainer.party[3].pbLearnMove(:SURF) + $Trainer.party[3].pbLearnMove(:DIVE) + $Trainer.party[3].pbLearnMove(:WATERFALL) + $Trainer.party[4].pbLearnMove(:DIG) + $Trainer.party[4].pbLearnMove(:CUT) + $Trainer.party[4].pbLearnMove(:HEADBUTT) + $Trainer.party[4].pbLearnMove(:ROCKSMASH) + $Trainer.party[5].pbLearnMove(:SOFTBOILED) + $Trainer.party[5].pbLearnMove(:STRENGTH) + $Trainer.party[5].pbLearnMove(:SWEETSCENT) + for i in 0...party.length + $Trainer.party[i].pbRecordFirstMoves + end +end + +def pbBoxesFull? + return !$Trainer || ($Trainer.party.length==6 && $PokemonStorage.full?) +end + +def pbNickname(pokemon) + speciesname=PBSpecies.getName(pokemon.species) + if Kernel.pbConfirmMessage(_INTL("Would you like to give a nickname to {1}?",speciesname)) + helptext=_INTL("{1}'s nickname?",speciesname) + newname=pbEnterPokemonName(helptext,0,10,"",pokemon) + pokemon.name=newname if newname!="" + end +end + +def pbStorePokemon(pokemon) + if pbBoxesFull? + Kernel.pbMessage(_INTL("There's no more room for Pokémon!\1")) + Kernel.pbMessage(_INTL("The Pokémon Boxes are full and can't accept any more!")) + return + end + pokemon.pbRecordFirstMoves + if $Trainer.party.length<6 + $Trainer.party[$Trainer.party.length]=pokemon + else + oldcurbox=$PokemonStorage.currentBox + storedbox=$PokemonStorage.pbStoreCaught(pokemon) + curboxname=$PokemonStorage[oldcurbox].name + boxname=$PokemonStorage[storedbox].name + creator=nil + creator=Kernel.pbGetStorageCreator if $PokemonGlobal.seenStorageCreator + if storedbox!=oldcurbox + if creator + Kernel.pbMessage(_INTL("Box \"{1}\" on {2}'s PC was full.\1",curboxname,creator)) + else + Kernel.pbMessage(_INTL("Box \"{1}\" on someone's PC was full.\1",curboxname)) + end + Kernel.pbMessage(_INTL("{1} was transferred to box \"{2}.\"",pokemon.name,boxname)) + else + if creator + Kernel.pbMessage(_INTL("{1} was transferred to {2}'s PC.\1",pokemon.name,creator)) + else + Kernel.pbMessage(_INTL("{1} was transferred to someone's PC.\1",pokemon.name)) + end + Kernel.pbMessage(_INTL("It was stored in box \"{1}.\"",boxname)) + end + end +end + +def pbNicknameAndStore(pokemon) + if pbBoxesFull? + Kernel.pbMessage(_INTL("There's no more room for Pokémon!\1")) + Kernel.pbMessage(_INTL("The Pokémon Boxes are full and can't accept any more!")) + return + end + $Trainer.seen[pokemon.species]=true + $Trainer.owned[pokemon.species]=true + pbNickname(pokemon) + pbStorePokemon(pokemon) +end + +def pbAddPokemon(pokemon,level=nil,seeform=true) + return if !pokemon || !$Trainer + if pbBoxesFull? + Kernel.pbMessage(_INTL("There's no more room for Pokémon!\1")) + Kernel.pbMessage(_INTL("The Pokémon Boxes are full and can't accept any more!")) + return false + end + if pokemon.is_a?(String) || pokemon.is_a?(Symbol) + pokemon=getID(PBSpecies,pokemon) + end + if pokemon.is_a?(Integer) && level.is_a?(Integer) + pokemon=PokeBattle_Pokemon.new(pokemon,level,$Trainer) + end + speciesname=PBSpecies.getName(pokemon.species) + if $Trainer.party.length==0 + pbMEPlay("fffirstpokemon") + Kernel.pbMessage(_INTL("{1} obtained {2}!\1",$Trainer.name,speciesname)) + else + pbMEPlay("PokemonGet") + Kernel.pbMessage(_INTL("{1} obtained {2}!\1",$Trainer.name,speciesname)) + end + pbNicknameAndStore(pokemon) + pbSeenForm(pokemon) if seeform + return true +end + +def pbAddPokemonSilent(pokemon,level=nil,seeform=true) + return false if !pokemon || pbBoxesFull? || !$Trainer + if pokemon.is_a?(String) || pokemon.is_a?(Symbol) + pokemon=getID(PBSpecies,pokemon) + end + if pokemon.is_a?(Integer) && level.is_a?(Integer) + pokemon=PokeBattle_Pokemon.new(pokemon,level,$Trainer) + end + $Trainer.seen[pokemon.species]=true + $Trainer.owned[pokemon.species]=true + pbSeenForm(pokemon) if seeform + pokemon.pbRecordFirstMoves + if $Trainer.party.length<6 + $Trainer.party[$Trainer.party.length]=pokemon + else + $PokemonStorage.pbStoreCaught(pokemon) + end + return true +end + +def pbAddToParty(pokemon,level=nil,seeform=true) + return false if !pokemon || !$Trainer || $Trainer.party.length>=6 + if pokemon.is_a?(String) || pokemon.is_a?(Symbol) + pokemon=getID(PBSpecies,pokemon) + end + if pokemon.is_a?(Integer) && level.is_a?(Integer) + pokemon=PokeBattle_Pokemon.new(pokemon,level,$Trainer) + end + speciesname=PBSpecies.getName(pokemon.species) + Kernel.pbMessage(_INTL("{1} obtained {2}!\\se[PokemonGet]\1",$Trainer.name,speciesname)) + pbNicknameAndStore(pokemon) + pbSeenForm(pokemon) if seeform + return true +end + +def pbAddToPartySilent(pokemon,level=nil,seeform=true) + return false if !pokemon || !$Trainer || $Trainer.party.length>=6 + if pokemon.is_a?(String) || pokemon.is_a?(Symbol) + pokemon=getID(PBSpecies,pokemon) + end + if pokemon.is_a?(Integer) && level.is_a?(Integer) + pokemon=PokeBattle_Pokemon.new(pokemon,level,$Trainer) + end + $Trainer.seen[pokemon.species]=true + $Trainer.owned[pokemon.species]=true + pbSeenForm(pokemon) if seeform + pokemon.pbRecordFirstMoves + $Trainer.party[$Trainer.party.length]=pokemon + return true +end + +def pbAddForeignPokemon(pokemon,level=nil,ownerName=nil,nickname=nil,ownerGender=0,seeform=true) + return false if !pokemon || !$Trainer || $Trainer.party.length>=6 + if pokemon.is_a?(String) || pokemon.is_a?(Symbol) + pokemon=getID(PBSpecies,pokemon) + end + if pokemon.is_a?(Integer) && level.is_a?(Integer) + pokemon=PokeBattle_Pokemon.new(pokemon,level,$Trainer) + end + # Set original trainer to a foreign one (if ID isn't already foreign) + if pokemon.trainerID==$Trainer.id + pokemon.trainerID=$Trainer.getForeignID + pokemon.ot=ownerName if ownerName && ownerName!="" + pokemon.otgender=ownerGender + end + # Set nickname + pokemon.name=nickname[0,10] if nickname && nickname!="" + # Recalculate stats + pokemon.calcStats + if ownerName + Kernel.pbMessage(_INTL("{1} received a Pokémon from {2}.\\se[PokemonGet]\1",$Trainer.name,ownerName)) + else + Kernel.pbMessage(_INTL("{1} received a Pokémon.\\se[PokemonGet]\1",$Trainer.name)) + end + pbStorePokemon(pokemon) + $Trainer.seen[pokemon.species]=true + $Trainer.owned[pokemon.species]=true + pbSeenForm(pokemon) if seeform + return true +end + +def pbGenerateEgg(pokemon,text="") + return false if !pokemon || !$Trainer || $Trainer.party.length>=6 + if pokemon.is_a?(String) || pokemon.is_a?(Symbol) + pokemon=getID(PBSpecies,pokemon) + end + if pokemon.is_a?(Integer) + pokemon=PokeBattle_Pokemon.new(pokemon,EGGINITIALLEVEL,$Trainer) + end + # Get egg steps + dexdata=pbOpenDexData + pbDexDataOffset(dexdata,pokemon.species,21) + eggsteps=dexdata.fgetw + dexdata.close + # Set egg's details + pokemon.name=_INTL("Egg") + pokemon.eggsteps=eggsteps + pokemon.obtainText=text + pokemon.calcStats + # Add egg to party + $Trainer.party[$Trainer.party.length]=pokemon + return true +end + +def pbRemovePokemonAt(index) + return false if index<0 || !$Trainer || index>=$Trainer.party.length + haveAble=false + for i in 0...$Trainer.party.length + next if i==index + haveAble=true if $Trainer.party[i].hp>0 && !$Trainer.party[i].isEgg? + end + return false if !haveAble + $Trainer.party.delete_at(index) + return true +end + +def pbSeenForm(poke,gender=0,form=0) + $Trainer.formseen=[] if !$Trainer.formseen + $Trainer.formlastseen=[] if !$Trainer.formlastseen + if poke.is_a?(String) || poke.is_a?(Symbol) + poke=getID(PBSpecies,poke) + end + if poke.is_a?(PokeBattle_Pokemon) + gender=poke.gender + form=(poke.form rescue 0) + species=poke.species + else + species=poke + end + return if !species || species<=0 + gender=0 if gender>1 + formnames=pbGetMessage(MessageTypes::FormNames,species) + form=0 if !formnames || formnames=="" #Emily 7-23 + $Trainer.formseen[species]=[[],[]] if !$Trainer.formseen[species] + $Trainer.formseen[species][gender][form]=true + $Trainer.formlastseen[species]=[] if !$Trainer.formlastseen[species] + $Trainer.formlastseen[species]=[gender,form] if $Trainer.formlastseen[species]==[] +end + + + +################################################################################ +# Analysing Pokémon +################################################################################ +# Heals all Pokémon in the party. +def pbHealAll + return if !$Trainer + for i in $Trainer.party + i.heal + end +end + +# Returns the first unfainted, non-egg Pokémon in the player's party. +def pbFirstAblePokemon(variableNumber) + for i in 0...$Trainer.party.length + p=$Trainer.party[i] + if p && !p.isEgg? && p.hp>0 + pbSet(variableNumber,i) + return $Trainer.party[i] + end + end + pbSet(variableNumber,-1) + return nil +end + +# Checks whether the player would still have an unfainted Pokémon if the +# Pokémon given by _pokemonIndex_ were removed from the party. +def pbCheckAble(pokemonIndex) + for i in 0...$Trainer.party.length + p=$Trainer.party[i] + next if i==pokemonIndex + return true if p && !p.isEgg? && p.hp>0 + end + return false +end + +# Returns true if there are no usable Pokémon in the player's party. +def pbAllFainted + for i in $Trainer.party + return false if !i.isEgg? && i.hp>0 + end + return true +end + +def pbBalancedLevel(party) + return 1 if party.length==0 + # Calculate the mean of all levels + sum=0 + party.each{|p| sum+=p.level } + return 1 if sum==0 + average=sum.to_f/party.length.to_f + # Calculate the standard deviation + varianceTimesN=0 + for i in 0...party.length + deviation=party[i].level-average + varianceTimesN+=deviation*deviation + end + # Note: This is the "population" standard deviation calculation, since no + # sample is being taken + stdev=Math.sqrt(varianceTimesN/party.length) + mean=0 + weights=[] + # Skew weights according to standard deviation + for i in 0...party.length + weight=party[i].level.to_f/sum.to_f + if weight<0.5 + weight-=(stdev/PBExperience::MAXLEVEL.to_f) + weight=0.001 if weight<=0.001 + else + weight+=(stdev/PBExperience::MAXLEVEL.to_f) + weight=0.999 if weight>=0.999 + end + weights.push(weight) + end + weightSum=0 + weights.each{|weight| weightSum+=weight } + # Calculate the weighted mean, assigning each weight to each level's + # contribution to the sum + for i in 0...party.length + mean+=party[i].level*weights[i] + end + mean/=weightSum + # Round to nearest number + mean=mean.round + # Adjust level to minimum + mean=1 if mean<1 + # Add 2 to the mean to challenge the player + mean+=2 + # Adjust level to maximum + mean=PBExperience::MAXLEVEL if mean>PBExperience::MAXLEVEL + return mean +end + +# Returns the Pokémon's size in millimeters. +def pbSize(pokemon) + dexdata=pbOpenDexData + pbDexDataOffset(dexdata,pokemon.species,33) + baseheight=dexdata.fgetw # Gets the base height in tenths of a meter + dexdata.close + hpiv=pokemon.iv[0]&15 + ativ=pokemon.iv[1]&15 + dfiv=pokemon.iv[2]&15 + spiv=pokemon.iv[3]&15 + saiv=pokemon.iv[4]&15 + sdiv=pokemon.iv[5]&15 + m=pokemon.personalID&0xFF + n=(pokemon.personalID>>8)&0xFF + s=(((ativ^dfiv)*hpiv)^m)*256+(((saiv^sdiv)*spiv)^n) + xyz=[] + if s<10 + xyz=[290,1,0] + elsif s<110 + xyz=[300,1,10] + elsif s<310 + xyz=[400,2,110] + elsif s<710 + xyz=[500,4,310] + elsif s<2710 + xyz=[600,20,710] + elsif s<7710 + xyz=[700,50,2710] + elsif s<17710 + xyz=[800,100,7710] + elsif s<32710 + xyz=[900,150,17710] + elsif s<47710 + xyz=[1000,150,32710] + elsif s<57710 + xyz=[1100,100,47710] + elsif s<62710 + xyz=[1200,50,57710] + elsif s<64710 + xyz=[1300,20,62710] + elsif s<65210 + xyz=[1400,5,64710] + elsif s<65410 + xyz=[1500,2,65210] + else + xyz=[1700,1,65510] + end + return (((s-xyz[2])/xyz[1]+xyz[0]).floor*baseheight/10).floor +end + +# Returns true if the given species can be legitimately obtained as an egg. +def pbHasEgg?(species) + if species.is_a?(String) || species.is_a?(Symbol) + species=getID(PBSpecies,species) + end + evospecies=pbGetEvolvedFormData(species) + compatspecies=(evospecies && evospecies[0]) ? evospecies[0][2] : species + dexdata=pbOpenDexData + pbDexDataOffset(dexdata,compatspecies,31) + compat1=dexdata.fgetb # Get egg group 1 of this species + compat2=dexdata.fgetb # Get egg group 2 of this species + dexdata.close + return false if isConst?(compat1,PBEggGroups,:Ditto) || + isConst?(compat1,PBEggGroups,:Undiscovered) || + isConst?(compat2,PBEggGroups,:Ditto) || + isConst?(compat2,PBEggGroups,:Undiscovered) + baby=pbGetBabySpecies(species) + return true if species==baby # Is a basic species + baby=pbGetBabySpecies(species,0,0) + return true if species==baby # Is an egg species without incense + return false +end + + + +################################################################################ +# Look through Pokémon in storage, choose a Pokémon in the party +################################################################################ +# Yields every Pokémon/egg in storage in turn. +def pbEachPokemon + for i in -1...$PokemonStorage.maxBoxes + for j in 0...$PokemonStorage.maxPokemon(i) + poke=$PokemonStorage[i][j] + yield(poke,i) if poke + end + end +end + +# Yields every Pokémon in storage in turn. +def pbEachNonEggPokemon + pbEachPokemon{|pokemon,box| + yield(pokemon,box) if !pokemon.isEgg? + } +end + +# Choose a Pokémon/egg from the party. +# Stores result in variable _variableNumber_ and the chosen Pokémon's name in +# variable _nameVarNumber_; result is -1 if no Pokémon was chosen +def pbChoosePokemon(variableNumber,nameVarNumber,ableProc=nil, allowIneligible=false) + chosen=0 + pbFadeOutIn(99999){ + scene=PokemonScreen_Scene.new + screen=PokemonScreen.new(scene,$Trainer.party) + if ableProc + chosen=screen.pbChooseAblePokemon(ableProc,allowIneligible) + else + screen.pbStartScene(_INTL("Choose a Pokémon."),false) + chosen=screen.pbChoosePokemon + screen.pbEndScene + end + } + pbSet(variableNumber,chosen) + if chosen>=0 + pbSet(nameVarNumber,$Trainer.party[chosen].name) + else + pbSet(nameVarNumber,"") + end +end + +def pbChooseNonEggPokemon(variableNumber,nameVarNumber) + pbChoosePokemon(variableNumber,nameVarNumber,proc {|poke| + !poke.isEgg? + }) +end + +def pbChooseAblePokemon(variableNumber,nameVarNumber) + pbChoosePokemon(variableNumber,nameVarNumber,proc {|poke| + !poke.isEgg? && poke.hp>0 + }) +end + +def pbChoosePokemonForTrade(variableNumber,nameVarNumber,wanted) + pbChoosePokemon(variableNumber,nameVarNumber,proc {|poke| + if wanted.is_a?(String) || wanted.is_a?(Symbol) + wanted=getID(PBSpecies,wanted) + end + return !poke.isEgg? && !(poke.isShadow? rescue false) && poke.species==wanted + }) +end + + + +################################################################################ +# Checks through the party for something +################################################################################ +def pbHasSpecies?(species) + if species.is_a?(String) || species.is_a?(Symbol) + species=getID(PBSpecies,species) + end + for pokemon in $Trainer.party + next if pokemon.isEgg? + return true if pokemon.species==species + end + return false +end + +def pbHasFatefulSpecies?(species) + if species.is_a?(String) || species.is_a?(Symbol) + species=getID(PBSpecies,species) + end + for pokemon in $Trainer.party + next if pokemon.isEgg? + return true if pokemon.species==species && pokemon.obtainMode==4 + end + return false +end + +def pbHasType?(type) + if type.is_a?(String) || type.is_a?(Symbol) + type=getID(PBTypes,type) + end + for pokemon in $Trainer.party + next if pokemon.isEgg? + return true if pokemon.hasType?(type) + end + return false +end + +# Checks whether any Pokémon in the party knows the given move, and returns +# the index of that Pokémon, or nil if no Pokémon has that move. +def pbCheckMove(move) + move=getID(PBMoves,move) + return nil if !move || move<=0 + for i in $Trainer.party + next if i.isEgg? + for j in i.moves + return i if j.id==move + end + end + return nil +end + + + +################################################################################ +# Regional and National Pokédexes +################################################################################ +# Gets the Regional Pokédex number of the national species for the specified +# Regional Dex. The parameter "region" is zero-based. For example, if two +# regions are defined, they would each be specified as 0 and 1. +def pbGetRegionalNumber(region, nationalSpecies) + if nationalSpecies<=0 || nationalSpecies>PBSpecies.maxValue + # Return 0 if national species is outside range + return 0 + end + pbRgssOpen("Data/regionals.dat","rb"){|f| + numRegions=f.fgetw + numDexDatas=f.fgetw + if region>=0 && region=0 && region=0 && region=$PokemonGlobal.pokedexUnlocked.length-1 + if $Trainer.pokedexSeen(region)>0 + $PokemonGlobal.pokedexViable[0]=region + end + else + numDexes=$PokemonGlobal.pokedexUnlocked.length + case numDexes + when 1 # National Dex only + if $PokemonGlobal.pokedexUnlocked[0] + if $Trainer.pokedexSeen>0 + $PokemonGlobal.pokedexViable.push(0) + end + end + else # Regional dexes + National Dex + for i in 0...numDexes + regionToCheck=(i==numDexes-1) ? -1 : i + if $PokemonGlobal.pokedexUnlocked[i] + if $Trainer.pokedexSeen(regionToCheck)>0 + $PokemonGlobal.pokedexViable.push(i) + end + end + end + end + end +end + +# Unlocks a Dex list. The National Dex is -1 here (or nil argument). +def pbUnlockDex(dex=-1) + index=dex + index=$PokemonGlobal.pokedexUnlocked.length-1 if index<0 + index=$PokemonGlobal.pokedexUnlocked.length-1 if index>$PokemonGlobal.pokedexUnlocked.length-1 + $PokemonGlobal.pokedexUnlocked[index]=true +end + +# Locks a Dex list. The National Dex is -1 here (or nil argument). +def pbLockDex(dex=-1) + index=dex + index=$PokemonGlobal.pokedexUnlocked.length-1 if index<0 + index=$PokemonGlobal.pokedexUnlocked.length-1 if index>$PokemonGlobal.pokedexUnlocked.length-1 + $PokemonGlobal.pokedexUnlocked[index]=false +end + + + +################################################################################ +# Other utilities +################################################################################ +def pbTextEntry(helptext,minlength,maxlength,variableNumber) + $game_variables[variableNumber]=pbEnterText(helptext,minlength,maxlength) + $game_map.need_refresh = true if $game_map +end + +def pbMoveTutorAnnotations(move,movelist=nil) + ret=[] + for i in 0...6 + ret[i]=nil + next if i>=$Trainer.party.length + found=false + for j in 0...4 + if !$Trainer.party[i].isEgg? && $Trainer.party[i].moves[j].id==move + ret[i]=_INTL("LEARNED") + found=true + end + end + next if found + species=$Trainer.party[i].species + if !$Trainer.party[i].isEgg? && movelist && movelist.any?{|j| j==species } + # Checked data from movelist + ret[i]=_INTL("ABLE") + elsif !$Trainer.party[i].isEgg? && $Trainer.party[i].isCompatibleWithMove?(move) + # Checked data from PBS/tm.txt + ret[i]=_INTL("ABLE") + else + ret[i]=_INTL("NOT ABLE") + end + end + return ret +end + +def pbMoveTutorChoose(move,movelist=nil,bymachine=false) + ret=false + if move.is_a?(String) || move.is_a?(Symbol) + move=getID(PBMoves,move) + end + if movelist!=nil && movelist.is_a?(Array) + for i in 0...movelist.length + if movelist[i].is_a?(String) || movelist[i].is_a?(Symbol) + movelist[i]=getID(PBSpecies,movelist[i]) + end + end + end + pbFadeOutIn(99999){ + scene=PokemonScreen_Scene.new + movename=PBMoves.getName(move) + screen=PokemonScreen.new(scene,$Trainer.party) + annot=pbMoveTutorAnnotations(move,movelist) + screen.pbStartScene(_INTL("Teach which Pokémon?"),false,annot) + loop do + chosen=screen.pbChoosePokemon + if chosen>=0 + pokemon=$Trainer.party[chosen] + if pokemon.isEgg? + Kernel.pbMessage(_INTL("{1} can't be taught to an Egg.",movename)) + elsif (pokemon.isShadow? rescue false) + Kernel.pbMessage(_INTL("Shadow Pokémon can't be taught any moves.")) + elsif movelist && !movelist.any?{|j| j==pokemon.species } + Kernel.pbMessage(_INTL("{1} and {2} are not compatible.",pokemon.name,movename)) + Kernel.pbMessage(_INTL("{1} can't be learned.",movename)) + elsif !pokemon.isCompatibleWithMove?(move) + Kernel.pbMessage(_INTL("{1} and {2} are not compatible.",pokemon.name,movename)) + Kernel.pbMessage(_INTL("{1} can't be learned.",movename)) + else + if pbLearnMove(pokemon,move,false,bymachine) + ret=true + break + end + end + else + break + end + end + screen.pbEndScene + } + return ret # Returns whether the move was learned by a Pokemon +end + +def pbChooseMove(pokemon,variableNumber,nameVarNumber) + return if !pokemon + ret=-1 + pbFadeOutIn(99999){ + scene=PokemonSummaryScene.new + screen=PokemonSummary.new(scene) + ret=screen.pbStartForgetScreen([pokemon],0,0) + } + $game_variables[variableNumber]=ret + if ret>=0 + $game_variables[nameVarNumber]=PBMoves.getName(pokemon.moves[ret].id) + else + $game_variables[nameVarNumber]="" + end + $game_map.need_refresh = true if $game_map +end + +# Opens the Pokémon screen +def pbPokemonScreen + return if !$Trainer + sscene=PokemonScreen_Scene.new + sscreen=PokemonScreen.new(sscene,$Trainer.party) + pbFadeOutIn(99999) { sscreen.pbPokemonScreen } +end + +def pbSaveScreen + ret=false + scene=PokemonSaveScene.new + screen=PokemonSave.new(scene) + ret=screen.pbSaveScreen + return ret +end + +def pbConvertItemToItem(variable,array) + item=pbGet(variable) + pbSet(variable,0) + for i in 0...(array.length/2) + if isConst?(item,PBItems,array[2*i]) + pbSet(variable,getID(PBItems,array[2*i+1])) + return + end + end +end + +def pbConvertItemToPokemon(variable,array) + item=pbGet(variable) + pbSet(variable,0) + for i in 0...(array.length/2) + if isConst?(item,PBItems,array[2*i]) + pbSet(variable,getID(PBSpecies,array[2*i+1])) + return + end + end +end + + + + +class PokemonGlobalMetadata + attr_accessor :trainerRecording +end + + + +def pbRecordTrainer + wave=pbRecord(nil,10) + if wave + $PokemonGlobal.trainerRecording=wave + return true + end + return false +end diff --git a/Kawariki-patches/ports/dummy_Audio_EX2.rb b/Kawariki-patches/ports/dummy_Audio_EX2.rb new file mode 100644 index 0000000..2f1669c --- /dev/null +++ b/Kawariki-patches/ports/dummy_Audio_EX2.rb @@ -0,0 +1,1745 @@ +# =========================================================================== +# ★★ WF-RGSS Scripts ★★ +# Audio-EX オーディオストリーム再生スクリプト +# バージョン : rev-18.1(2013-4-13) +# 作者 : A Crying Minister (WHITE-FLUTE) +# サポート先URI: http://www.whiteflute.org/wfrgss/ +# --------------------------------------------------------------------------- +# 機能: +# ・ogg ファイルのストリーム再生をサポートします。 +# ・RPGツクール(R)VX準拠のループ再生をサポートします。 +# ・再生位置の記憶、呼び出しにより +# 途中からの再生を行う事が出来るようになります。 +# ・様々なエフェクトにより更なる表現力をもたらします。 +# ・戦闘曲からマップBGMへ遷移するとき、曲の途中再生を行います。 +# ・曲の途中で任意のタイミングから曲をロードできます。 +# --------------------------------------------------------------------------- +# 制限事項: +# ・現時点では、oggファイルしかサポートしていません。 +# --------------------------------------------------------------------------- +# 設置場所 :共通スクリプト より下、Mainより上 +# 必要スクリプト: +# ・共通スクリプト、共通実行スクリプト +# 必要DLL: +# ・wfAudio.dll +# 注意事項: +# ▽Audio-ProtectEXと併用はできません。 +# ▽VXAce環境ではExit-Exと併用してください。 +# +# 著作権表記: +# ogg_static, vorbis_static, vorbisfile_static +# Copyright (C) 1994-2002 XIPHOPHORUS Company. +#============================================================================== +# =begin +# オーディオ保護スクリプトと対応はしていない +if defined? WFRGSS_AudioProtectEX + raise "It is not possible to use simultaneously with this script." +end + +module WFRGSS_AudioEX +# --------------------------------------------------------------------------- +# バトルBGMの途中再生を行うかを指定します。 + BATTLE_DURING_PLAYBACK = false + +# --------------------------------------------------------------------------- +# マップBGMをバトルBGMに設定するスイッチIDを指定します。 + REPEAT_BATTLE_FLAG = 292 + +# --------------------------------------------------------------------------- +# パックファイルを使用するかを指定します。 + USE_PACKFILE = true + +# --------------------------------------------------------------------------- +# パックファイルのファイルパスを指定します。 + # BGM_PACKFILE = "./Audio/bgm.bin" + BGM_PACKFILE = "./Audio/bgm.bin" + BGS_PACKFILE = "./Audio/bgs.bin" + ME_PACKFILE = "./Audio/me.bin" + SE_PACKFILE = "./Audio/se.bin" + + +# --------------------------------------------------------------------------- + REMOVE_BGM_PATH = (/Audio\/BGM\//i).freeze + REMOVE_BGS_PATH = (/Audio\/BGS\//i).freeze + REMOVE_ME_PATH = (/Audio\/ME\//i).freeze + REMOVE_SE_PATH = (/Audio\/SE\//i).freeze +end + + + +# F12 リセット対策 +unless defined? Audio::BGM + + module Audio + begin + @audio_init = Win32API.new('System/wfAudio','initialize','v','l') + @audio_dispose = Win32API.new('System/wfAudio','dispose','v','v') + @audio_play = Win32API.new('System/wfAudio','playBGM',%w(p i i p),'l') + @audio_meplay = Win32API.new('System/wfAudio','playME',%w(p i i),'l') + @audio_stop = Win32API.new('System/wfAudio','stop','v','v') + @audio_stopsecond = Win32API.new('System/wfAudio','stopSecond','v','v') + @audio_stopb = Win32API.new('System/wfAudio','stopBoth','v','v') + @audio_stopme = Win32API.new('System/wfAudio','stopME','v','v') + @audio_memory = Win32API.new('System/wfAudio','SeekMemorise','v','n') + @audio_set = Win32API.new('System/wfAudio','SeekSet','v','n') + @audio_clear = Win32API.new('System/wfAudio','SeekClear','v','v') + @audio_pause = Win32API.new('System/wfAudio','Pause','v','v') + @audio_resume = Win32API.new('System/wfAudio','Resume','v','v') + @audio_cross = Win32API.new('System/wfAudio','setCrossFade','i','l') + @audio_bgmfade = Win32API.new('System/wfAudio','fadeBGM','i','l') + @audio_bgsfade = Win32API.new('System/wfAudio','fadeBGS','i','l') + @audio_mefade = Win32API.new('System/wfAudio','fadeME','i','l') + @audio_rtp = Win32API.new('System/wfAudio','getRTPPath',%w(p p i),'l') + @audio_plays = Win32API.new('System/wfAudio' , 'playSecond' , %w(p i i i p) , 'l') + @audio_balance = Win32API.new('System/wfAudio','balanceSet','i','v') + @audio_seplay = Win32API.new('System/wfAudio','playSE',%w(p i i),'l') + @audio_stopse = Win32API.new('System/wfAudio','stopSE','v','v') + @audio_bgsplay = Win32API.new('System/wfAudio','playBGS',%w(p i i p),'l') + @audio_stopbgs = Win32API.new('System/wfAudio','stopBGS','v','v') + @audio_play_m = Win32API.new('System/wfAudio','playBGMMemory',%w(p p i i p),'l') + @audio_plays_m = Win32API.new('System/wfAudio' , 'playSecondMemory' , %w(p p i i i p) , 'l') + @audio_bgsplay_m = Win32API.new('System/wfAudio','playBGSMemory',%w(p p i i p),'l') + @audio_meplay_m = Win32API.new('System/wfAudio','playMEMemory',%w(p p i i),'l') + @audio_seplay_m = Win32API.new('System/wfAudio','playSEMemory',%w(p p i i),'l') + @audio_isbgm = Win32API.new('System/wfAudio','isPrimaryBGMPosition','v','n') + @audio_isbgms = Win32API.new('System/wfAudio','isSecoundryBGMPosition','v','n') + @audio_isbgs = Win32API.new('System/wfAudio','isBGSPosition','v','n') + @audio_bgmpan = Win32API.new('System/wfAudio','primaryBGMPan','i','v') + @audio_secondpan = Win32API.new('System/wfAudio','secondryBGMPan','i','v') + @audio_bgspan = Win32API.new('System/wfAudio','BGSPan','i','v') + @audio_mepan = Win32API.new('System/wfAudio','MEPan','i','v') + @audio_sepan = Win32API.new('System/wfAudio','SEPan','i','v') + @audio_pbgmreverb = Win32API.new('System/wfAudio','primaryBGMReverb',%w(i i),'v') + @audio_sbgmreverb = Win32API.new('System/wfAudio','secondryBGMReverb',%w(i i),'v') + @audio_bgsreverb = Win32API.new('System/wfAudio','BGSReverb',%w(i i),'v') + @audio_mereverb = Win32API.new('System/wfAudio','MEReverb',%w(i i),'v') + @audio_sereverb = Win32API.new('System/wfAudio','SEReverb',%w(i i),'v') + @audio_pbgmeffect = Win32API.new('System/wfAudio','primaryBGMEffect',%w(i i),'v') + @audio_sbgmeffect = Win32API.new('System/wfAudio','secondryBGMEffect',%w(i i),'v') + @audio_bgseffect = Win32API.new('System/wfAudio','BGSEffect',%w(i i),'v') + @audio_meeffect = Win32API.new('System/wfAudio','MEEffect',%w(i i),'v') + @audio_seeffect = Win32API.new('System/wfAudio','SEEffect',%w(i i),'v') + @audio_pbgmeffectd = Win32API.new('System/wfAudio','primaryBGMEffectDetail',%w(i i),'v') + @audio_sbgmeffectd = Win32API.new('System/wfAudio','secondryBGMEffectDetail',%w(i i),'v') + @audio_bgseffectd = Win32API.new('System/wfAudio','BGSEffectDetail',%w(i i),'v') + @audio_meeffectd = Win32API.new('System/wfAudio','MEEffectDetail',%w(i i),'v') + @audio_seeffectd = Win32API.new('System/wfAudio','SEEffectDetail',%w(i i),'v') + + @audio_vceffectd = Win32API.new('System/wfAudio','voiceEffectDetail',%w(i i),'v') + @audio_vceffect = Win32API.new('System/wfAudio','voiceEffect',%w(i i),'v') + @audio_vcreverb = Win32API.new('System/wfAudio','voiceReverb',%w(i i),'v') + @audio_vcpan = Win32API.new('System/wfAudio','voicePan','i','v') + @audio_isvcn = Win32API.new('System/wfAudio','isVoicePositionNumber','i','n') + @audio_isvcf = Win32API.new('System/wfAudio','isVoicePositionFileName','p','n') + @audio_vclenn = Win32API.new('System/wfAudio','voiceLengthNumber','i','n') + @audio_vclenf = Win32API.new('System/wfAudio','voiceLengthFileName','p','n') + @audio_vcpingn = Win32API.new('System/wfAudio','isVoicePlayingNumber','i','i') + @audio_vcpingf = Win32API.new('System/wfAudio','isVoicePlayingFileName','p','i') + @audio_stopvc = Win32API.new('System/wfAudio','stopVoice','v','v') + @audio_vcplay = Win32API.new('System/wfAudio','playVoice',%w(p i i i),'l') + @audio_vcplay_m = Win32API.new('System/wfAudio','playVoiceMemory',%w(p p i i i),'l') + + @audio_pbgmlen = Win32API.new('System/wfAudio','primaryBGMLength','v','n') + @audio_sbgmlen = Win32API.new('System/wfAudio','secondryBGMLength','v','n') + @audio_bgslen = Win32API.new('System/wfAudio','BGSLength','v','n') + @audio_pbgmloop = Win32API.new('System/wfAudio','isPrimaryBGMLoopEnd','v','i') + @audio_sbgmloop = Win32API.new('System/wfAudio','isSecondryBGMLoopEnd','v','i') + @audio_bgsloop = Win32API.new('System/wfAudio','isBGSLoopEnd','v','i') + @audio_vcpermiln = Win32API.new('System/wfAudio','isVoicePositionPermilNumber','i','i') + @audio_vcpermilf = Win32API.new('System/wfAudio','isVoicePositionPermilFileName','p','i') + + @audio_pbgmcheck = Win32API.new('System/wfAudio','isPrimaryBGMCheckPoint','l','i') + @audio_sbgmcheck = Win32API.new('System/wfAudio','isSecoundryBGMCheckPoint','l','i') + @audio_bgscheck = Win32API.new('System/wfAudio','isBGSCheckPoint','l','i') + @audio_cue = Win32API.new('System/wfAudio','cuePrimaryBGM','v','v') + @audio_pbgm_reserve = Win32API.new('System/wfAudio','reservePrimaryBGM',%w(p i i l l i),'l') + @audio_pbgm_reserve_m = Win32API.new('System/wfAudio','reservePrimaryBGMMemory',%w(p p i i l l i),'l') + rescue Exception + raise if debug? + raise(LoadError,"cannot read modules.(wfAudio.dll)") + end + + begin + @audio_mci = Win32API.new('winmm', 'mciSendString', %w(p p l l),'i') + rescue Exception + raise if debug? + raise(LoadError,"cannot read modules.(winmm.dll)") + end + begin + # ini ファイルから、セクションのキーを取得するAPI + @audio_gpps = Win32API.new('kernel32', + 'GetPrivateProfileStringA',%w(p p p p l p),'l').freeze + rescue Exception + raise if debug? + raise(LoadError,"cannot read modules.(kernel32.dll)") + end + + @audio__init = false + @audio_bgm = Hash.new + @audio_bgs = Hash.new + @audio_me = Hash.new + + @audio_t = nil + @audio_tbgm = nil + @audio_tbgs = nil + # ----------------------------------------------------------------------- + # この定数は削除しないでください。 + # この定数を削除してF12によるリセットを行うと、 + # 例外 SystemStackError が発生します。 + BGM = true + + # ----------------------------------------------------------------------- + # リバーブのタイプ + # ※一般的に遠くに聞こえるようになり、音量が小さくなります。 + # ※聞こえにくいものもあります。 + REVERB_DEFAULT = 0 # デフォルト + REVERB_GENERIC = 1 # 一般 + REVERB_PADDEDCELL = 2 # クッション壁のある部屋 + REVERB_ROOM = 3 # 室内 + REVERB_BATHROOM = 4 # バスルーム + REVERB_LIVINGROOM = 5 # リビングルーム + REVERB_STONEROOM = 6 # 石造りの部屋 + REVERB_AUDITORIUM = 7 # 講堂 + REVERB_CONCERTHALL = 8 # コンサートホール + REVERB_CAVE = 9 # 洞窟 + REVERB_ARENA = 10 # 競技場 + REVERB_HANGAR = 11 # 格納庫 + REVERB_CARPETEDHALLWAY = 12 # 絨毯敷きの通路 + REVERB_HALLWAY = 13 # 廊下 + REVERB_STONECORRIDOR = 14 # 石造りの廊下 + REVERB_ALLEY = 15 # 路地 + REVERB_FOREST = 16 # 森 + REVERB_CITY = 17 # 街・都会 + REVERB_MOUNTAINS = 18 # 山中 + REVERB_QUARRY = 19 # 採石場 + REVERB_PLAIN = 20 # 平原 + REVERB_PARKINGLOT = 21 # 駐車場 + REVERB_SEWERPIPE = 22 # 下水管内 + REVERB_UNDERWATER = 23 # 水中 + REVERB_SMALLROOM = 24 # 小部屋 + REVERB_MEDIUMROOM = 25 # 中部屋 + REVERB_LARGEROOM = 26 # 大部屋 + REVERB_MEDIUMHALL = 27 # 中ホール + REVERB_LARGEHALL = 28 # 大ホール + REVERB_PLATE = 29 # プレートリバーブ + # ----------------------------------------------------------------------- + # エフェクト + OFF = 0 # オフ(リバーブ使用可) + ECHO = 1 # エコー + CHORUS = 2 # コーラス + FLANGER = 3 # フランジャー + GARGLE = 4 # ガーグル + DISTORTION = 5 # ディストーション + COMPRESSOR = 6 # コンプレッション + PARAMEQ = 7 # パラメトリック・イコライザー + + # ----------------------------------------------------------------------- + # 波形 + WAVE_TRIANGLE = 0 # 三角波 + WAVE_SIN = 1 # サイン波 + WAVE_SQUARE = 1 # 矩形波 + + # ----------------------------------------------------------------------- + # フェイズ + PHASE_NEG_180 = 0 # -180° + PHASE_NEG_90 = 1 # -90° + PHASE_ZERO = 2 # 0° + PHASE_90 = 3 # 90° + PHASE_180 = 4 # 180° + + # ----------------------------------------------------------------------- + # エコー + ECHO_WETDRYMIX = 0 # 0~10000 (0.00%~100.00%) + ECHO_FEEDBACK = 1 # 0~10000 (0.00%~100.00%) + ECHO_LEFTDELAY = 2 # 100~200000 (1ms~2000ms) + ECHO_RIGHTDELAY = 3 # 100~200000 (1ms~2000ms) + ECHO_PANDELAY = 4 # 0~1 + + # ----------------------------------------------------------------------- + # コーラス + CHORUS_WETDRYMIX = 0 # 0~10000 (0.00%~100.00%) + CHORUS_DEPTH = 1 # 0~10000 (0.00%~100.00%) + CHORUS_FEEDBACK = 2 # -9900~9900 (-99.00%~99.00%) + CHORUS_FREQUENCY = 3 # 0~10 + CHORUS_WAVE_FORM = 4 # 波形(0~1)三角波・サイン波 + CHORUS_PHASE = 5 # フェイズ(0~4) + CHORUS_DELAY = 6 # 0~2000 + + # ----------------------------------------------------------------------- + # フランジャー + FLANGER_WETDRYMIX = 0 # 0~10000 (0.00%~100.00%) + FLANGER_DEPTH = 1 # 0~10000 (0.00%~100.00%) + FLANGER_FEEDBACK = 2 # -9900~9900 (-99.00%~99.00%) + FLANGER_FREQUENCY = 3 # 0~10 + FLANGER_WAVE_FORM = 4 # 波形(0~1)三角波・サイン波 + FLANGER_PHASE = 5 # フェイズ(0~4) + FLANGER_DELAY = 6 # 0~400 + + # ----------------------------------------------------------------------- + # ガーグル + GARGLE_RATEHZ = 0 # 1~1000(Hz) + GARGLE_WAVE_FORM = 1 # 波形(0~1)三角波・矩形波 + + # ----------------------------------------------------------------------- + # ディストーション + DISTORTION_GAIN = 0 # -6000~0(-60.00dB~0.00dB) + DISTORTION_EDGE = 1 # 0~10000 (0.00%~100.00%) + DISTORTION_POSTEQCENTERFREQUENCY = 2 # 10000~800000(100Hz~8000Hz) + DISTORTION_POSTEQBANDWIDTH = 3 # 10000~800000(100Hz~8000Hz) + DISTORTION_PRELOWPASSCUTOFF = 4 # 10000~800000(100Hz~8000Hz) + + # ----------------------------------------------------------------------- + # コンプレッション + COMPRESSOR_GAIN = 0 # -6000~6000(-60.00dB~60.00dB) + COMPRESSOR_ATTACK = 1 # 1~50000(0.01ms~500ms) + COMPRESSOR_RELEASE = 2 # 5000~300000(50ms~3000ms) + COMPRESSOR_THRESHOLD = 3 # -6000~0(-60.00dB~0.00dB) + COMPRESSOR_RATIO = 4 # 100~10000(1n:m~100n:m) + COMPRESSOR_PREDELAY = 5 # 0~400 + + # ----------------------------------------------------------------------- + # パラメトリック・イコライザー + PARAMEQ_CENTER = 0 # 8000~1600000(80Hz~16000Hz) + PARAMEQ_BANDWIDTH = 1 # 100~3600(1~36) + PARAMEQ_GAIN = 2 # -1500~1500(-15.00dB~15.00dB) + + end + + class << Audio + # ------------------------------------------------------------------------- + # ● データの取得 + # ------------------------------------------------------------------------- + def Audio.get + get_file( "Audio/BGM/" , @audio_bgm ) + get_file( "Audio/BGS/" , @audio_bgs ) + get_file( "Audio/ME/" , @audio_me ) + if rpgvx? + if rpgvxace? + ret = "\x00" * 256 + @audio_gpps.call('Game',"RTP",'' , ret , 255 , '.\Game.ini' ) + ret.tr!("\x00","") + return if ret.empty? + path = "\x00" * 301 + @audio_rtp.call( ret , path , 3 ) + path.force_encoding("ASCII-8BIT") + # p "Audio.get path = #{path}" + for j in 0...path.size + break if path[j] == "\x00" + end + path = path[0...j] + return if path.empty? + path = String.ansi2utf8( path ) + path.gsub!(/\x00/){""} + path.gsub!(/\\/){"/"} + # p "Audio.get path = #{path}" + # RTPのインストール情報はレジストリの値に設定されてるっぽい add zorome + get_file( "#{path}/Audio/BGM/" , @audio_bgm ) + get_file( "#{path}/Audio/BGS/" , @audio_bgs ) + get_file( "#{path}/Audio/ME/" , @audio_me ) + else + ret = "\x00" * 256 + @audio_gpps.call('Game',"RTP",'' , ret , 255 , '.\Game.ini' ) + ret.tr!("\x00","") + return if ret.empty? + path = "\x00" * 301 + @audio_rtp.call( ret , path , 2 ) + for j in 0...path.size + break if path[j] == 0 + end + path = path[0...j] + return if path.empty? + path = String.ansi2utf8( path ) + path.gsub!(/\x00/){""} + path.gsub!(/\\/){"/"} + get_file( "#{path}/Audio/BGM/" , @audio_bgm ) + get_file( "#{path}/Audio/BGS/" , @audio_bgs ) + get_file( "#{path}/Audio/ME/" , @audio_me ) + end + else + for i in 1..3 + ret = "\x00" * 256 + @audio_gpps.call('Game',"RTP#{i}",'' , ret , 255 , '.\Game.ini' ) + ret.tr!("\x00","") + next if ret.empty? + path = "\x00" * 301 + @audio_rtp.call( ret , path , 1 ) + for j in 0...path.size + break if path[j] == 0 + end + path = path[0...j] + next if path.empty? + path = String.ansi2utf8( path ) + path.gsub!(/\x00/){""} + path.gsub!(/\\/){"/"} + get_file( "#{path}/Audio/BGM/" , @audio_bgm ) + get_file( "#{path}/Audio/BGS/" , @audio_bgs ) + get_file( "#{path}/Audio/ME/" , @audio_me ) + end + end + end + # ------------------------------------------------------------------------- + # ● データの取得 + # ------------------------------------------------------------------------- + def Audio.get_file( path , data ) + Dir.foreach( path ) do | file | + xpath = File.basename(file,File.extname(file)) + if rpgvxace? && xpath.encoding.name != "UTF-8" + xpath = String.ansi2utf8( xpath ) + xpath.gsub!(/\x00/){""} + end + bfile = path + xpath + next if xpath == "." or xpath == ".." + data[bfile] = File.extname(file).downcase + end + rescue SystemCallError + end + # ------------------------------------------------------------------------- + # ● プライマリBGMの長さ + # ------------------------------------------------------------------------- + def Audio.primary_bgm_length + @audio_pbgmlen.call() if init_check + end + # ------------------------------------------------------------------------- + # ● セカンダリBGMの長さ + # ------------------------------------------------------------------------- + def Audio.secondry_bgm_length + @audio_sbgmlen.call() if init_check + end + # ------------------------------------------------------------------------- + # ● BGSの長さ + # ------------------------------------------------------------------------- + def Audio.bgs_length + @audio_bgslen.call() if init_check + end + # ------------------------------------------------------------------------- + # ● プライマリBGMループ + # ------------------------------------------------------------------------- + def Audio.primary_bgm_loop? + (@audio_pbgmloop.call() == 1) if init_check + end + # ------------------------------------------------------------------------- + # ● セカンダリBGMループ + # ------------------------------------------------------------------------- + def Audio.secondry_bgm_loop? + (@audio_sbgmloop.call() == 1) if init_check + end + # ------------------------------------------------------------------------- + # ● BGSループ + # ------------------------------------------------------------------------- + def Audio.bgs_loop? + (@audio_bgsloop.call() == 1) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのエフェクト + # ------------------------------------------------------------------------- + def Audio.bgm_effect( type , effect ) + # p "※Audio_EX2 Audio.bgm_effect start" # add zorome + @audio_pbgmeffect.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのエフェクト + # ------------------------------------------------------------------------- + def Audio.second_effect( type , effect ) + @audio_sbgmeffect.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGSのエフェクト + # ------------------------------------------------------------------------- + def Audio.bgs_effect( type , effect ) + @audio_bgseffect.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● MEのエフェクト + # ------------------------------------------------------------------------- +#~ def Audio.me_effect( type , effect ) +#~ @audio_meeffect.call( type , effect ? 1 : 0 ) if init_check +#~ end + # ------------------------------------------------------------------------- + # ● SEのエフェクト + # ------------------------------------------------------------------------- + def Audio.se_effect( type , effect ) + @audio_seeffect.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスのエフェクト + # ------------------------------------------------------------------------- + def Audio.voice_effect( type , effect ) + @audio_vceffect.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのエフェクト 詳細 + # ------------------------------------------------------------------------- + def Audio.bgm_effect_detail( item , value ) + # p "※Audio_EX2 Audio.bgm_effect_detail start" # add zorome + @audio_pbgmeffectd.call( item , value ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのエフェクト 詳細 + # ------------------------------------------------------------------------- + def Audio.second_effect_detail( item , value ) + @audio_sbgmeffectd.call( item , value ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGSのエフェクト 詳細 + # ------------------------------------------------------------------------- + def Audio.bgs_effect_detail( item , value ) + @audio_bgseffectd.call( item , value ) if init_check + end + # ------------------------------------------------------------------------- + # ● MEのエフェクト 詳細 + # ------------------------------------------------------------------------- +#~ def Audio.me_effect_detail( item , value ) +#~ @audio_meeffectd.call( item , value ) if init_check +#~ end + # ------------------------------------------------------------------------- + # ● SEのエフェクト 詳細 + # ------------------------------------------------------------------------- + def Audio.se_effect_detail( item , value ) + @audio_seeffectd.call( item , value ) if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスのエフェクト 詳細 + # ------------------------------------------------------------------------- + def Audio.voice_effect_detail( item , value ) + @audio_vceffectd.call( item , value ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのリバーブ + # ------------------------------------------------------------------------- + def Audio.bgm_reverb( type , effect ) + # p "※Audio_EX2 Audio.bgm_reverb start" # add zorome + @audio_pbgmreverb.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのリバーブ + # ------------------------------------------------------------------------- + def Audio.second_reverb( type , effect ) + @audio_sbgmreverb.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGSのリバーブ + # ------------------------------------------------------------------------- + def Audio.bgs_reverb( type , effect ) + @audio_bgsreverb.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● MEのリバーブ + # ------------------------------------------------------------------------- +#~ def Audio.me_reverb( type , effect ) +#~ @audio_mereverb.call( type , effect ? 1 : 0 ) if init_check +#~ end + # ------------------------------------------------------------------------- + # ● SEのリバーブ + # ------------------------------------------------------------------------- + def Audio.se_reverb( type , effect ) + @audio_sereverb.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスのリバーブ + # ------------------------------------------------------------------------- + def Audio.voice_reverb( type , effect ) + @audio_vcreverb.call( type , effect ? 1 : 0 ) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMの定位 + # ------------------------------------------------------------------------- + def Audio.bgm_pan( pan ) + # p "※Audio_EX2 Audio.bgm_pan start" # add zorome + @audio_bgmpan.call(pan) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMの定位 + # ------------------------------------------------------------------------- + def Audio.second_pan( pan ) + @audio_secondpan.call(pan) if init_check + end + # ------------------------------------------------------------------------- + # ● BGSの定位 + # ------------------------------------------------------------------------- + def Audio.bgs_pan( pan ) + @audio_bgspan.call(pan) if init_check + end + # ------------------------------------------------------------------------- + # ● MEの定位 + # ------------------------------------------------------------------------- + def Audio.me_pan( pan ) + @audio_mepan.call(pan) if init_check + end + # ------------------------------------------------------------------------- + # ● SEの定位 + # ------------------------------------------------------------------------- + def Audio.se_pan( pan ) + @audio_sepan.call(pan) if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスの定位 + # ------------------------------------------------------------------------- + def Audio.voice_pan( pan ) + @audio_vcpan.call(pan) if init_check + end + # ------------------------------------------------------------------------- + # ● BGMのフェードイン + # ------------------------------------------------------------------------- + def Audio.bgm_fadein( bgm ,fadetime ,volume ,pitch ) + # p "※Audio_EX2 Audio.bgm_fadein start" # add zorome + tm = 0.01 + tm = fadetime.to_f / volume.to_f if volume > 0 + @audio_tbgm = Thread.new do + loop do + break unless @audio_t + sleep(0.01) + end + _bgm_play( bgm , 0 , pitch ) + for i in 1..volume + sleep(tm) + _bgm_play( bgm , i, pitch ) + end + Graphics.frame_reset + @audio_tbgm = nil + end + Graphics.frame_reset + end + # ------------------------------------------------------------------------- + # ● BGSのフェードイン + # ------------------------------------------------------------------------- + def Audio.bgs_fadein( bgs ,fadetime ,volume ,pitch ) + tm = 0.01 + tm = fadetime.to_f / volume.to_f if volume > 0 + @audio_tbgs = Thread.new do + loop do + break unless @audio_t + sleep(0.01) + end + _bgs_play( bgs , 0 , pitch ) + for i in 1..volume + sleep(tm) + _bgs_play( bgs , i, pitch ) + end + Graphics.frame_reset + @audio_tbgs = nil + end + Graphics.frame_reset + end + # ------------------------------------------------------------------------- + # ● BGMのフェードイン + # ------------------------------------------------------------------------- + def Audio.bgm_fadein? + # p "※Audio_EX2 Audio.bgm_fadein? start" # add zorome + @audio_tbgm + end + # ------------------------------------------------------------------------- + # ● BGSのフェードイン + # ------------------------------------------------------------------------- + def Audio.bgs_fadein? + @audio_tbgs + end + # ------------------------------------------------------------------------- + # ● 初期化 + # ------------------------------------------------------------------------- + def Audio.ogg_init +#~ p "Audio.ogg_init" + unless @audio__init +#~ p "Audio.ogg_init unless" + ret = @audio_init.call() + end + @audio__init = true if ret == 0 + end + # ------------------------------------------------------------------------- + # ● 解放 + # ------------------------------------------------------------------------- + def Audio.dispose +#~ p "Audio.dispose" + if init_check +#~ p "Audio.dispose init_check true" + @audio_dispose.call() + end + @audio__init = false + end + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + def Audio.is_primary_bgm_position + @audio_isbgm.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + def Audio.is_secondry_bgm_position + @audio_isbgms.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + def Audio.is_bgs_position + @audio_isbgs.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + alias :__wfrgss_audio_bgm_pos__ :bgm_pos if rpgvxace? + def Audio.bgm_pos +#~ p "※Audio_EX2 Audio.bgm_pos start" # add zorome + pos = is_primary_bgm_position + # pos = __wfrgss_audio_bgm_pos__ if pos.zero? && rpgvxace? + pos + end + private(:__wfrgss_audio_bgm_pos__) if rpgvxace? + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + alias :__wfrgss_audio_bgs_pos__ :bgs_pos if rpgvxace? + def Audio.bgs_pos + pos = is_bgs_position + # pos = __wfrgss_audio_bgs_pos__ if pos.zero? && rpgvxace? + pos + end + private(:__wfrgss_audio_bgs_pos__) if rpgvxace? + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + def Audio.is_voice_position_number(n) + @audio_isvcn.call(n) if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置取得 + # ------------------------------------------------------------------------- + def Audio.is_voice_position_filename(filename) + val = 0 + val = @audio_isvcf.call(String.utf82ansi(filename + '.ogg')) if init_check + if val <= 0 + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_SE_PATH){ "" } + return @audio_isvcf.call(String.utf82ansi(filenames + '.ogg')) if init_check + return val + else + return val + end + end + # ------------------------------------------------------------------------- + # ● ボイスの長さ取得 + # ------------------------------------------------------------------------- + def Audio.voice_length_number(n) + @audio_vclenn.call(n) if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスの長さ取得 + # ------------------------------------------------------------------------- + def Audio.voice_length_filename(filename) + val = 0 + val = @audio_vclenf.call(String.utf82ansi(filename + '.ogg')) if init_check + if val <= 0 + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_SE_PATH){ "" } + return @audio_vclenf.call(String.utf82ansi(filenames + '.ogg')) if init_check + return val + else + return val + end + end + # ------------------------------------------------------------------------- + # ● ボイスの演奏中か? + # ------------------------------------------------------------------------- + def Audio.voice_playing_number?(n) + @audio_vcpingn.call(n) != 0 if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスの演奏中か? + # ------------------------------------------------------------------------- + def Audio.voice_playing_filename?(filename) + val = 0 + val = @audio_vcpingf.call(String.utf82ansi(filename + '.ogg')) if init_check + if val.zero? + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_SE_PATH){ "" } + return @audio_vcpingf.call(String.utf82ansi(filenames + '.ogg')) != 0 if init_check + return false + else + return true + end + end + # ------------------------------------------------------------------------- + # ● ボイスの進行度 ‰ + # ------------------------------------------------------------------------- + def Audio.voice_permil_number(n) + @audio_vcpermiln.call(n) if init_check + end + # ------------------------------------------------------------------------- + # ● ボイスの進行度 ‰ + # ------------------------------------------------------------------------- + def Audio.voice_permil_filename(filename) + val = 0 + val = @audio_vcpermilf.call(String.utf82ansi(filename + '.ogg')) if init_check + if val.zero? + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_SE_PATH){ "" } + return @audio_vcpermilf.call(String.utf82ansi(filenames + '.ogg')) if init_check + return val + else + return val + end + end + # ------------------------------------------------------------------------- + # ● プライマリBGM チェックポイント + # ------------------------------------------------------------------------- + def Audio.is_primary_bgm_checkpoint( checkpoint ) + # p "※Audio_EX2 Audio.is_primary_bgm_checkpoint start" # add zorome + @audio_pbgmcheck.call(checkpoint) != 0 if init_check + end + # ------------------------------------------------------------------------- + # ● セカンダリBGM チェックポイント + # ------------------------------------------------------------------------- + def Audio.is_secoundry_bgm_checkpoint( checkpoint ) + @audio_sbgmcheck.call(checkpoint) != 0 if init_check + end + # ------------------------------------------------------------------------- + # ● BGS チェックポイント + # ------------------------------------------------------------------------- + def Audio.is_bgs_checkpoint( checkpoint ) + @audio_bgscheck.call(checkpoint) != 0 if init_check + end + # ------------------------------------------------------------------------- + # ● プライマリBGM キュー + # ------------------------------------------------------------------------- + def Audio.primary_bgm_cue + # p "※Audio_EX2 Audio.primary_bgm_cue start" # add zorome + @audio_cue.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 一時停止 + # ------------------------------------------------------------------------- + def Audio.pause + # p "※Audio_EX2 Audio.pause start" # add zorome + @audio_pause.call() if init_check + end + # ------------------------------------------------------------------------- + # ● レジューム + # ------------------------------------------------------------------------- + def Audio.resume + # p "※Audio_EX2 Audio.resume start" # add zorome + @audio_resume.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置記憶 + # ------------------------------------------------------------------------- + def Audio.seek_memorise + @audio_memory.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置セット + # ------------------------------------------------------------------------- + def Audio.seek_set + @audio_set.call() if init_check + end + # ------------------------------------------------------------------------- + # ● 再生位置クリア + # ------------------------------------------------------------------------- + def Audio.seek_clear + @audio_clear.call() if init_check + end + # ------------------------------------------------------------------------- + # ● クロスフェードの設定 + # ------------------------------------------------------------------------- + def Audio.cross_fade( fade_time ) +#~ p "※Audio_EX2 Audio.cross_fade start" # add zorome + @audio_cross.call( fade_time ) if init_check + end + # ------------------------------------------------------------------------- + # ● ヘッダの参照 + # ------------------------------------------------------------------------- + def Audio.open_file( filename , ext ) + fns = f = data = nil + begin + fns = filename + ext + f = File.open(fns,"rb") + data = f.read(32) + f.close + f = nil + rescue Exception => errs + return "" + ensure + f.close unless f.closed? if f + f = nil + end + data + end + # ------------------------------------------------------------------------- + # ● ヘッダの参照 + # ------------------------------------------------------------------------- + def Audio.rtp_open_file_bgm( filename ) + for key in @audio_bgm.keys + if key.match(/#{filename}$/) + ext = @audio_bgm[key] +#~ p "rtp_open_file_bgm ext = #{ext}" # 拡張子 +#~ p "rtp_open_file_bgm key = #{key}" # ファイルのパス + return key + ext if self.check( self.open_file( key , ext )) + end + end + return "" + end + # ------------------------------------------------------------------------- + # ● ヘッダの参照 + # ------------------------------------------------------------------------- + def Audio.rtp_open_file_bgs( filename ) + for key in @audio_bgs.keys + if key.match(/#{filename}$/) + ext = @audio_bgs[key] + return key + ext if self.check( self.open_file( key , ext )) + end + end + return "" + end + # ------------------------------------------------------------------------- + # ● ヘッダの参照 + # ------------------------------------------------------------------------- +#~ def Audio.rtp_open_file_me( filename ) +#~ for key in @audio_me.keys +#~ if key.match(/#{filename}$/) +#~ ext = @audio_me[key] +#~ return key + ext if self.check( self.open_file( key , ext )) +#~ end +#~ end +#~ return "" +#~ end + # ------------------------------------------------------------------------- + # ● ヘッダの参照 + # ------------------------------------------------------------------------- + def Audio.check( data ) + return false unless @audio__init + unless rpgvxace? + if data[0] == 0x4f and data[1] == 0x67 and + data[2] == 0x67 and data[3] == 0x53 + return true + end + else + if data[0] == 'O' and data[1] == 'g' and + data[2] == 'g' and data[3] == 'S' + return true + end + end + false # other + end + #-------------------------------------------------------------------------- + # ● すべての音を止める + #-------------------------------------------------------------------------- + def Audio.all_noteoff + # p "※Audio_EX2 Audio.all_noteoff start" # add zorome + bgm_stop + bgs_stop + me_stop + se_stop + voice_stop + end + #-------------------------------------------------------------------------- + # ● セカンダリBGM の演奏を開始します + #-------------------------------------------------------------------------- + def Audio.second_play( filename, volume = 100, pitch = 100 , + balance = 50, pos = 0) + # p "※Audio_EX2 Audio.second_play start" # add zorome + init_check + if WFRGSS_AudioEX::USE_PACKFILE + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_BGM_PATH){ "" } + __original_bgm_stop__ + r = @audio_plays_m.call(String.utf82ansi(filenames + '.ogg'), + WFRGSS_AudioEX::BGM_PACKFILE, + volume,pitch,balance,[pos].pack("q")) + return if r.zero? + end + ext = @audio_bgm[ filename ] + ext = "" if ext.nil? + if self.check( self.open_file(filename,ext) ) + __original_bgm_stop__ + @audio_plays.call(String.utf82ansi('./' + filename + '.ogg'), + volume,pitch,balance,[pos].pack("q")) + elsif not (path = self.rtp_open_file_bgm( filename )).empty? + __original_bgm_stop__ + @audio_plays.call(String.utf82ansi(path),volume,pitch,balance, + [pos].pack("q")) + end + end + #-------------------------------------------------------------------------- + # ● セカンダリBGMのバランス + #-------------------------------------------------------------------------- + def Audio.balance( balance = 50 ) + @audio_balance.call(balance) if init_check + end + #-------------------------------------------------------------------------- + # ● セカンダリBGMの停止 + #-------------------------------------------------------------------------- + def Audio.second_stop + @audio_stopsecond.call() if init_check + end + #-------------------------------------------------------------------------- + # ● BGMの予約 + #-------------------------------------------------------------------------- + def Audio.bgm_reserve(filename, checkpoint, autoplay = false, + volume = 100, pitch = 100, pos = 0) + # p "※Audio_EX2 Audio.bgm_reserve start" # add zorome + init_check + if WFRGSS_AudioEX::USE_PACKFILE + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_BGM_PATH){ "" } + r = @audio_pbgm_reserve_m.call(String.utf82ansi(filenames + '.ogg'), + WFRGSS_AudioEX::BGM_PACKFILE, + volume,pitch,pos,checkpoint,autoplay ? 1 : 0) + return if r.zero? + end + ext = @audio_bgm[ filename ] + ext = "" if ext.nil? + if self.check( self.open_file(filename,ext) ) + @audio_pbgm_reserve.call(String.utf82ansi('./' + filename + '.ogg'), + volume,pitch,pos,checkpoint,autoplay ? 1 : 0) + elsif not (path = self.rtp_open_file_bgm( filename )).empty? + @audio_pbgm_reserve.call(String.utf82ansi(path),volume,pitch, + pos,checkpoint,autoplay ? 1 : 0) + end + end # def + #-------------------------------------------------------------------------- + # ● BGM の演奏を開始します + #-------------------------------------------------------------------------- + alias :__original_bgm_play__ :bgm_play + def Audio.bgm_play(filename, volume = 100, pitch = 100, pos = 0) +#~ p "※Audio_EX2 Audio.bgm_play start" # add zorome + begin + @audio_tbgm.kill if @audio_tbgm + rescue + nil + ensure + @audio_tbgm = nil + end + _bgm_play(filename, volume, pitch, pos) +#~ p "※Audio_EX2 bgm_play filename = #{filename}" + # p "pos = #{pos}" +#~ p "@audio_bgm = #{@audio_bgm}" +#~ p "@audio_tbgm = #{@audio_tbgm}" + # p "※Audio_EX2 volume = #{volume}" +#~ p "※Audio_EX2 Audio.bgm_play end" # add zorome + end # def + + private(:__original_bgm_play__) + def Audio._bgm_play(filename, volume = 100, pitch = 100, pos = 0) + init_check + if WFRGSS_AudioEX::USE_PACKFILE + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_BGM_PATH){ "" } + # p "filename = #{filename}" +#~ p "filenames = #{filenames}" + puts filenames + '.ogg' + puts WFRGSS_AudioEX::BGM_PACKFILE, + # volume,pitch,[pos].pack("q") + # r = @audio_play_m.call(String.utf82ansi(filenames + '.ogg'), + # WFRGSS_AudioEX::BGM_PACKFILE, + # volume,pitch,[pos].pack("q")) + r = 0 + # if r.zero? + # # p "ファイルなし1 RTP以外 くさい" + # __original_bgm_stop__ + # return + # end + end + # たぶんRTP + ext = @audio_bgm[ filename ] + ext = "" if ext.nil? +#~ p "ext = #{ext}" + if self.check( self.open_file(filename,ext) ) +#~ p "※※Audio_EX2 filename = #{filename}" + __original_bgm_stop__ + @audio_play.call(String.utf82ansi('./' + filename + '.ogg'), + volume,pitch,[pos].pack("q")) + # 従来の再生 + # __original_bgm_play__(filename, volume, pitch, pos) + elsif not (path = self.rtp_open_file_bgm( filename )).empty? +#~ p "ファイルなしじゃない?" +#~ p "path = #{path}" + __original_bgm_stop__ + @audio_play.call(String.utf82ansi(path),volume,pitch,[pos].pack("q")) + # 従来の再生 + # __original_bgm_play__(filename, volume, pitch, pos) + else + puts "ggg" + # @audio_stop.call() if init_check + # if pos > 0 + # # p "再生開始 1" + # __original_bgm_play__(filename, volume, pitch, pos) + # else + # # p "再生開始 2" + # __original_bgm_play__(filename, volume, pitch) + # end + end + end # def + private(:_bgm_play) + #-------------------------------------------------------------------------- + # ● BGM の演奏を停止します。 + #-------------------------------------------------------------------------- + alias :__original_bgm_stop__ :bgm_stop + def Audio.bgm_stop +#~ p "※Audio_EX2 Audio.bgm_stop start" # add zorome + @audio_stopb.call() if init_check + __original_bgm_stop__ +#~ p "※Audio_EX2 Audio.bgm_stop end" # add zorome + end + private(:__original_bgm_stop__) + #-------------------------------------------------------------------------- + # ● BGS の演奏を開始します + #-------------------------------------------------------------------------- + alias :__original_bgs_play__ :bgs_play + def Audio.bgs_play(filename, volume = 100, pitch = 100, pos = 0) + begin + @audio_tbgs.kill if @audio_tbgs + rescue + nil + ensure + @audio_tbgs = nil + end + _bgs_play(filename, volume, pitch, pos) + end + private(:__original_bgs_play__) + def Audio._bgs_play(filename, volume = 100, pitch = 100 , pos = 0) + init_check + if WFRGSS_AudioEX::USE_PACKFILE + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_BGS_PATH){ "" } + r = @audio_bgsplay_m.call(String.utf82ansi(filenames + '.ogg'), + WFRGSS_AudioEX::BGS_PACKFILE, + volume,pitch, [pos].pack("q")) + if r.zero? + __original_bgs_stop__ + return + end + end + ext = @audio_bgs[ filename ] + ext = "" if ext.nil? + if self.check( self.open_file(filename,ext) ) + __original_bgs_stop__ + @audio_bgsplay.call(String.utf82ansi('./' + filename + '.ogg'), + volume,pitch,[pos].pack("q")) + elsif not (path = self.rtp_open_file_bgs( filename )).empty? + __original_bgs_stop__ + @audio_bgsplay.call(String.utf82ansi(path),volume,pitch,[pos].pack("q")) + else + @audio_stopbgs.call() if init_check + if pos > 0 + __original_bgs_play__(filename, volume, pitch, pos) + else + __original_bgs_play__(filename, volume, pitch) + end + end + end + private(:_bgs_play) + #-------------------------------------------------------------------------- + # ● BGS の演奏を停止します。 + #-------------------------------------------------------------------------- + alias :__original_bgs_stop__ :bgs_stop + def Audio.bgs_stop + @audio_stopbgs.call() if init_check + __original_bgs_stop__ + end + private(:__original_bgs_stop__) + #-------------------------------------------------------------------------- + # ● ME の演奏を開始します。 + #-------------------------------------------------------------------------- +#~ alias :__original_me_play__ :me_play +#~ def Audio.me_play(filename, volume = 100, pitch = 100) +#~ p "※Audio_EX2 Audio.me_play start" # add zorome +#~ # init_check +#~ temp_chk = init_check +#~ p "temp_chk = #{temp_chk}" +#~ if WFRGSS_AudioEX::USE_PACKFILE +#~ filenames = filename.sub(WFRGSS_AudioEX::REMOVE_ME_PATH){ "" } +#~ r = @audio_meplay_m.call(String.utf82ansi(filenames + '.ogg'), +#~ WFRGSS_AudioEX::ME_PACKFILE, +#~ volume,pitch) +#~ if r.zero? +#~ p "ME ファイルなし1?" +#~ __original_me_stop__ +#~ return +#~ end +#~ end + # p "@audio_me = #{@audio_me}" +#~ p "@audio_t = #{@audio_t}" +#~ ext = @audio_me[ filename ] +#~ ext = "" if ext.nil? +#~ if self.check( self.open_file(filename,ext) ) +#~ __original_me_stop__ +#~ @audio_meplay.call(String.utf82ansi('./' + filename + '.ogg'),volume,pitch) +#~ elsif not (path = self.rtp_open_file_me( filename )).empty? +#~ __original_me_stop__ +#~ @audio_meplay.call(String.utf82ansi(path),volume,pitch) +#~ else +#~ @audio_stopme.call() if init_check +#~ me_sleep( filename ) +#~ __original_me_play__(filename, volume, pitch) +#~ end +#~ p "※Audio_EX2 Audio.me_play end" # add zorome +#~ end # def +#~ private(:__original_me_play__) + + #-------------------------------------------------------------------------- + # ● ME の演奏を開始します。add zorome + # 古いのを呼び出してみる + #-------------------------------------------------------------------------- +#~ def Audio.me_play_old(filename, volume = 100, pitch = 100) +#~ __original_me_play__(filename, volume, pitch) +#~ end # def + #-------------------------------------------------------------------------- + # ● ME の演奏を停止します。 + #-------------------------------------------------------------------------- +#~ alias :__original_me_stop__ :me_stop +#~ def Audio.me_stop +#~ p "※Audio_EX2 Audio.me_stop start" # add zorome +#~ # init_check +#~ temp_chk = init_check +#~ p "temp_chk = #{temp_chk}" +#~ @audio_stopme.call() +#~ __original_me_stop__ +#~ if @audio_t +#~ @audio_t.kill rescue nil +#~ @audio_t = nil +#~ Audio.resume +#~ end +#~ p "※Audio_EX2 Audio.me_stop end" # add zorome +#~ end # def +#~ private(:__original_me_stop__) + #-------------------------------------------------------------------------- + # ● SE の演奏を開始します。 + #-------------------------------------------------------------------------- + alias :__original_se_play__ :se_play + def Audio.se_play(filename, volume = 80, pitch = 100) + # p "Audio.se_play" + init_check + if WFRGSS_AudioEX::USE_PACKFILE + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_SE_PATH){ "" } + r = @audio_seplay_m.call(String.utf82ansi(filenames + '.ogg'), + WFRGSS_AudioEX::SE_PACKFILE, + volume,pitch) + # return if r >= 0 + return r=0 + end + # p "Audio.se_play2" + # p "filename = #{filename}" + __original_se_play__(filename, volume, pitch) + # p "Audio.se_play3" + end + #-------------------------------------------------------------------------- + # ● ボイス の演奏を開始します。 + #-------------------------------------------------------------------------- + def Audio.voice_play(filename, volume = 80, pitch = 100, number = -1) + init_check + if WFRGSS_AudioEX::USE_PACKFILE + filenames = filename.sub(WFRGSS_AudioEX::REMOVE_SE_PATH){ "" } + r = @audio_vcplay_m.call(String.utf82ansi(filenames + '.ogg'), + WFRGSS_AudioEX::SE_PACKFILE, + volume,pitch,number) + return if r >= 0 + end + @audio_vcplay.call(String.utf82ansi(filename + '.ogg'),volume,pitch,number) + end + private(:__original_se_play__) + #-------------------------------------------------------------------------- + # ● SE の演奏を停止します。 + #-------------------------------------------------------------------------- + alias :__original_se_stop__ :se_stop + def Audio.se_stop + @audio_stopse.call() if init_check + __original_se_stop__ + end + private(:__original_se_stop__) + #-------------------------------------------------------------------------- + # ● ボイス の演奏を停止します。 + #-------------------------------------------------------------------------- + def Audio.voice_stop + @audio_stopvc.call() if init_check + end + #-------------------------------------------------------------------------- + # ● BGMのフェードアウト + #-------------------------------------------------------------------------- + alias :__original_bgm_fade__ :bgm_fade + def Audio.bgm_fade( fadetime ) +#~ p "※Audio_EX2 Audio.bgm_fade start" # add zorome + __original_bgm_fade__(fadetime) + @audio_bgmfade.call(fadetime) if init_check + end + private(:__original_bgm_fade__) + #-------------------------------------------------------------------------- + # ● BGSのフェードアウト + #-------------------------------------------------------------------------- + alias :__original_bgs_fade__ :bgs_fade + def Audio.bgs_fade( fadetime ) + __original_bgs_fade__(fadetime) + @audio_bgsfade.call(fadetime) if init_check + end + private(:__original_bgs_fade__) + #-------------------------------------------------------------------------- + # ● MEのフェードアウト + #-------------------------------------------------------------------------- +#~ alias :__original_me_fade__ :me_fade +#~ def Audio.me_fade( fadetime ) +#~ __original_me_fade__(fadetime) +#~ @audio_mefade.call(fadetime) if init_check +#~ end +#~ private(:__original_me_fade__) + private + #-------------------------------------------------------------------------- + # ● MEの長さを取得して一時停止する + #-------------------------------------------------------------------------- +#~ def Audio.me_sleep( filename ) +#~ ext = "" +#~ filenames = "" +#~ for key in @audio_me.keys +#~ if key.match(/#{filename}$/) +#~ ext = @audio_me[key] +#~ filenames = key +#~ break +#~ end +#~ end +#~ case ext +#~ when ".mid" +#~ open = "Open \"#{filenames}#{ext}\" alias soundlength type sequencer" +#~ @audio_mci.call(open, "", 0, 0) +#~ +#~ set = "Set soundlength time format milliseconds" +#~ @audio_mci.call(set, "" , 0, 0) +#~ when ".wav" +#~ open = "Open \"#{filenames}#{ext}\" alias soundlength type waveaudio" +#~ @audio_mci.call(open, "", 0, 0) +#~ when ".mp3" +#~ open = "Open \"#{filenames}#{ext}\" alias soundlength type MPEGVideo" +#~ @audio_mci.call(open, "", 0, 0) +#~ else +#~ return +#~ end +#~ ret = "\x00" * 255 +#~ @audio_mci.call("status soundlength length", ret, 255, 0) +#~ @audio_mci.call("close soundlength", "", 0, 0) +#~ ret.tr!("\x00","") +#~ msec = ret.to_i +#~ sec = msec.to_f / 1000.0 +#~ @audio_t.kill if @audio_t +#~ @audio_t = Thread.new do +#~ Audio.pause +#~ Thread.pass +#~ sleep(sec) +#~ Audio.resume +#~ @audio_t = nil +#~ end +#~ end + #-------------------------------------------------------------------------- + # ● + #-------------------------------------------------------------------------- + def Audio.init_check + return true + # if @audio__init + raise "Initialization is required, has not been initialized." + end + end +end + +Audio.get + +unless rpgvxace? + class RPG::BGM < RPG::AudioFile + @@last = RPG::BGM.new + def play(pos = 0) + # p "※Audio_EX2 RPG::BGM play" # add zorome + if @name.empty? + Audio.bgm_stop + @@last = RPG::BGM.new + else + Audio.bgm_play('Audio/BGM/' + @name, @volume, @pitch, pos) + @@last = self.clone + end + end + def replay + play(@pos) + end + def self.stop + Audio.bgm_stop + @@last = RPG::BGM.new + end + def self.fade(time) + Audio.bgm_fade(time) + @@last = RPG::BGM.new + end + def self.last + @@last.pos = Audio.bgm_pos + @@last + end + attr_accessor :pos + end + class RPG::BGS < RPG::AudioFile + @@last = RPG::BGS.new + def play(pos = 0) + if @name.empty? + Audio.bgs_stop + @@last = RPG::BGS.new + else + Audio.bgs_play('Audio/BGS/' + @name, @volume, @pitch, pos) + @@last = self.clone + end + end + def replay + play(@pos) + end + def self.stop + Audio.bgs_stop + @@last = RPG::BGS.new + end + def self.fade(time) + Audio.bgs_fade(time) + @@last = RPG::BGS.new + end + def self.last + @@last.pos = Audio.bgs_pos + @@last + end + attr_accessor :pos + end +end + +class RPG::SBGM < RPG::AudioFile + @@last = RPG::SBGM.new + @@balance = 50 + def play(balance = @@balance, pos = 0) + # p "※Audio_EX2 RPG::SBGM play" # add zorome + if @name.empty? + Audio.second_stop + @@last = RPG::BGM.new + else + Audio.second_play('Audio/BGM/' + @name, @volume, @pitch, balance, pos) + @@last = self.clone + end + end + def replay(balance = @@balance) + play(balance ,@pos) + end + def self.stop + Audio.second_stop + @@last = RPG::SBGM.new + end + def self.fade(time) + Audio.bgm_fade(time) + @@last = RPG::SBGM.new + end + def self.balance( balance ) + @@balance = balance + Audio.balance(@@balance) + end + def self.last + @@last.pos = Audio.is_secondry_bgm_position + @@last + end + attr_accessor :pos +end + +# バトルBGMの途中再生を行うか? +if WFRGSS_AudioEX::BATTLE_DURING_PLAYBACK + +unless rpgvx? + class Game_System + #-------------------------------------------------------------------------- + # ● BGM の演奏 + # bgm : 演奏する BGM + #-------------------------------------------------------------------------- + def bgm_play(bgm) + @playing_bgm = bgm + if bgm && !bgm.name.empty? + playbgm = RPG::BGM.new(bgm.name, bgm.volume, bgm.pitch) + playbgm.play + else + RPG::BGM.stop + end + Graphics.frame_reset + end + #-------------------------------------------------------------------------- + # ● BGM の停止 + #-------------------------------------------------------------------------- + def bgm_stop + RPG::BGM.stop + end + #-------------------------------------------------------------------------- + # ● BGM のフェードアウト + # time : フェードアウト時間 (秒) + #-------------------------------------------------------------------------- + def bgm_fade(time) + @playing_bgm = nil + RPG::BGM.fade(time * 1000) + end + #-------------------------------------------------------------------------- + # ● BGM の記憶 + #-------------------------------------------------------------------------- + def bgm_memorize + @memorized_bgm = RPG::BGM.last + end + #-------------------------------------------------------------------------- + # ● BGM の復帰 + #-------------------------------------------------------------------------- + def bgm_restore + @memorized_bgm.replay + end + #-------------------------------------------------------------------------- + # ● BGS の演奏 + # bgs : 演奏する BGS + #-------------------------------------------------------------------------- + def bgs_play(bgs) + if bgs && !bgs.name.empty? + playbgs = RPG::BGS.new(bgs.name, bgs.volume, bgs.pitch) + playbgs.play + else + RPG::BGS.stop + end + Graphics.frame_reset + end + #-------------------------------------------------------------------------- + # ● BGS のフェードアウト + # time : フェードアウト時間 (秒) + #-------------------------------------------------------------------------- + def bgs_fade(time) + RPG::BGS.fade(time * 1000) + end + #-------------------------------------------------------------------------- + # ● BGS の記憶 + #-------------------------------------------------------------------------- + def bgs_memorize + @memorized_bgs = RPG::BGS.last + end + #-------------------------------------------------------------------------- + # ● BGS の復帰 + #-------------------------------------------------------------------------- + def bgs_restore + @memorized_bgs.replay + end + #-------------------------------------------------------------------------- + # ● 演奏中 BGM の取得 + #-------------------------------------------------------------------------- + def playing_bgm + RPG::BGM.last + end + #-------------------------------------------------------------------------- + # ● 演奏中 BGS の取得 + #-------------------------------------------------------------------------- + def playing_bgs + RPG::BGS.last + end + #-------------------------------------------------------------------------- + # ● BGM と BGS の保存 + #-------------------------------------------------------------------------- + def save_bgm_and_bgs + @map_bgm = RPG::BGM.last + @map_bgs = RPG::BGS.last + end + #-------------------------------------------------------------------------- + # ● BGM と BGS の再開 + #-------------------------------------------------------------------------- + def replay_bgm_and_bgs + # p "※Audio_EX2 replay_bgm_and_bgs" + @map_bgm.replay + @map_bgs.replay + end + end + + class Scene_Map + #-------------------------------------------------------------------------- + # ● バトルの呼び出し + #-------------------------------------------------------------------------- + def call_battle + $game_temp.battle_calling = false + $game_temp.menu_calling = false + $game_temp.menu_beep = false + $game_player.make_encounter_count + $game_temp.map_bgm = $game_system.playing_bgm + unless $game_switches[WFRGSS_AudioEX::REPEAT_BATTLE_FLAG] + $game_system.save_bgm_and_bgs + $game_system.bgm_stop + $game_system.se_play($data_system.battle_start_se) + $game_system.bgm_play($game_system.battle_bgm) + else + $game_system.se_play($data_system.battle_start_se) + end + $game_player.straighten + $scene = Scene_Battle.new + end + end + class Scene_Battle + #-------------------------------------------------------------------------- + # ● フレーム更新 (パーティコマンドフェーズ : 逃げる) + #-------------------------------------------------------------------------- + def update_phase2_escape + enemies_agi = 0 + enemies_number = 0 + for enemy in $game_troop.enemies + if enemy.exist? + enemies_agi += enemy.agi + enemies_number += 1 + end + end + enemies_agi /= enemies_number if enemies_number > 0 + actors_agi = 0 + actors_number = 0 + for actor in $game_party.actors + if actor.exist? + actors_agi += actor.agi + actors_number += 1 + end + end + actors_agi /= actors_number if actors_number > 0 + success = rand(100) < 50 * actors_agi / enemies_agi + if success + $game_system.se_play($data_system.escape_se) + unless $game_switches[WFRGSS_AudioEX::REPEAT_BATTLE_FLAG] + $game_system.replay_bgm_and_bgs + end + battle_end(1) + else + $game_party.clear_actions + start_phase4 + end + end + #-------------------------------------------------------------------------- + # ● アフターバトルフェーズ開始 + #-------------------------------------------------------------------------- + def start_phase5 + @phase = 5 + $game_system.me_play($game_system.battle_end_me) + unless $game_switches[WFRGSS_AudioEX::REPEAT_BATTLE_FLAG] + $game_system.replay_bgm_and_bgs + end + exp = 0 + gold = 0 + treasures = [] + for enemy in $game_troop.enemies + unless enemy.hidden + exp += enemy.exp + gold += enemy.gold + if rand(100) < enemy.treasure_prob + if enemy.item_id > 0 + treasures.push($data_items[enemy.item_id]) + end + if enemy.weapon_id > 0 + treasures.push($data_weapons[enemy.weapon_id]) + end + if enemy.armor_id > 0 + treasures.push($data_armors[enemy.armor_id]) + end + end + end + end + treasures = treasures[0..5] + for i in 0...$game_party.actors.size + actor = $game_party.actors[i] + if actor.cant_get_exp? == false + last_level = actor.level + actor.exp += exp + if actor.level > last_level + @status_window.level_up(i) + end + end + end + $game_party.gain_gold(gold) + for item in treasures + case item + when RPG::Item + $game_party.gain_item(item.id, 1) + when RPG::Weapon + $game_party.gain_weapon(item.id, 1) + when RPG::Armor + $game_party.gain_armor(item.id, 1) + end + end + @result_window = Window_BattleResult.new(exp, gold, treasures) + @phase5_wait_count = 100 + end + end +else + unless rpgvxace? + class Game_System + #------------------------------------------------------------------------ + # ● BGM と BGS の保存 + #------------------------------------------------------------------------ + def save_bgm_and_bgs + # p "※Audio_EX2 save_bgm_and_bgs" + @map_bgm = RPG::BGM.last + @map_bgs = RPG::BGS.last + end + #------------------------------------------------------------------------ + # ● BGM と BGS の再開 + #------------------------------------------------------------------------ + def replay_bgm_and_bgs + @map_bgm.replay + @map_bgs.replay + end + end + class Scene_Map < Scene_Base + #------------------------------------------------------------------------ + # ● バトル画面への切り替え + #------------------------------------------------------------------------ + def call_battle + @spriteset.update + Graphics.update + $game_player.make_encounter_count + $game_player.straighten + $game_system.save_bgm_and_bgs + unless $game_switches[WFRGSS_AudioEX::REPEAT_BATTLE_FLAG] + RPG::BGM.stop + RPG::BGS.stop + Sound.play_battle_start + $game_system.battle_bgm.play + else + Sound.play_battle_start + end + $game_temp.next_scene = nil + $scene = Scene_Battle.new + end + end + class Scene_Battle < Scene_Base + #------------------------------------------------------------------------ + # ● 勝利の処理 + #------------------------------------------------------------------------ + def process_victory + @info_viewport.visible = false + @message_window.visible = true + unless $game_switches[WFRGSS_AudioEX::REPEAT_BATTLE_FLAG] + RPG::BGM.stop + $game_system.battle_end_me.play + $game_system.replay_bgm_and_bgs unless $BTEST + else + $game_system.battle_end_me.play + end + display_exp_and_gold + display_drop_items + display_level_up + battle_end(0) + end + #------------------------------------------------------------------------ + # ● 戦闘終了 + # result : 結果 (0:勝利 1:逃走 2:敗北) + #------------------------------------------------------------------------ + def battle_end(result) + # p "※Audio_EX2 battle_end" + if result == 2 and not $game_troop.can_lose + call_gameover + else + $game_party.clear_actions + $game_party.remove_states_battle + $game_troop.clear + if $game_temp.battle_proc != nil + $game_temp.battle_proc.call(result) + $game_temp.battle_proc = nil + end + unless $game_switches[WFRGSS_AudioEX::REPEAT_BATTLE_FLAG] + $game_system.replay_bgm_and_bgs unless result.zero? unless $BTEST + end + $scene = Scene_Map.new + @message_window.clear + Graphics.fadeout(30) + end + $game_temp.in_battle = false + end + end + end +end + +end + +#~ if rpgvxace? +#~ alias __wfrgss_audioex_rgss_main__ rgss_main +#~ def rgss_main(&block) +#~ Audio.ogg_init +#~ __wfrgss_audioex_rgss_main__(&block) +#~ ensure +#~ Audio.dispose +#~ end +#~ end + +# =end diff --git a/Kawariki-patches/ports/lamp.rb b/Kawariki-patches/ports/lamp.rb new file mode 100644 index 0000000..3cdd1b8 --- /dev/null +++ b/Kawariki-patches/ports/lamp.rb @@ -0,0 +1,332 @@ +=begin + HN_Light version 1.0.1.2 for VX Ace +        by 半生 +http://www.tktkgame.com + + 要HN_RG_BITMAP(ver 0.1.2.1以降) + +2012/01/02 ver 1.0.1.2 + バグ修正 +2012/01/02 ver 1.0.1.0 + 隊列歩行の仲間に対応 + +=end + +# ----- ▽ 設定ここから ▽ ----- +module HN_Light + # 簡略化 0:(メタボ)~2:(荒めだけど軽い) + SIMPLIFY = 1 + + # プレイヤーの灯りタイプに使う変数番号 + PLAYER_LIGHT_TYPE = 48 + + # 仲間の灯りタイプに使う変数番号 + FOLLOWER_LIGHT_TYPE = 48 + + # 暗闇判定に使うスイッチ + DARK_SWITCH = 9 + + # 灯りイベント識別用の正規表現 + REGEX_LIGHT = /@灯り(\d+)/ + + # 灯り画像のディレクトリ + LIGHT_IMG_DIR = "Graphics/Pictures/" + + # 灯りBitmap設定 + LIGHTS = [ + # [FILENAME, CELLS, ZOOM, OFFSET_Y, HUE] + ["light5", 1, 5.5, 0, 0], + ["light5", 1, 15.0, 0, 0], + ["light3", 1, 0.8, 0, 0], + ["light4", 1, 1.0, -16, 0], + ["light5", 1, 2.0, 0, 0], + ["light6", 1, 10.0, 0, 0], + ["light7", 1, 5.0, -16, 0], + ["light8", 1, 5.0, -16, 0], + ["light9", 1, 2.0, -16, 0], + ["light4", 1, 3.0, -16, 0], + ] +end +# ----- △ 設定ここまで △ ----- + +module HN_Light + # イベントへのmix-in用 + module LightEvent + attr_reader :light_type + def initialize + super() + @light_type = 0 + end + + def check_light + @light_type = 0 + return if @list.nil? + @list.each do |command| + break if @light_type > 0 + if command.code == 108 or command.code == 408 + command.parameters.each do |line| + if line =~ REGEX_LIGHT + @light_type = $1.to_i + break + end + end + end + end # END @list.each + end + + end # END module LightEvent + + + class Light + attr_reader :bitmap + attr_reader :cells + attr_reader :width + attr_reader :height + attr_reader :ox + attr_reader :oy + def initialize(light_type, s_zoom = 1) + light = LIGHTS[light_type - 1] + if light.nil? + # 本来ならここには来ないはず + @bitmap = Bitmap.new(32, 32) + @cels = 1 + @zoom = 1.0 + @oy = 16 + @ox = 16 + @width = 32 + @height = 32 + else + @bitmap = Bitmap.new(LIGHT_IMG_DIR + light[0]) + @bitmap.invert() + @cells = light[1].to_i + @cells = 1 if (@cells < 1 or @cells > @bitmap.width) + @zoom = light[2].to_f + @zoom = 1.0 if @zoom <= 0.0 + @zoom /= s_zoom + @width = @bitmap.width / @cells + @height = @bitmap.height + + # 拡大縮小処理 + if @zoom != 1.0 + new_width = (@width * @zoom).round + new_height = (@height * @zoom).round + if new_width * new_height < 1 + @zoom = 1.0 + else + @width = new_width + @height = new_height + new_bitmap = Bitmap.new(@width * @cells, @height) + new_bitmap.stretch_blt(new_bitmap.rect,@bitmap, @bitmap.rect) + @bitmap.dispose + @bitmap = new_bitmap + end + end + @ox = @width / 2 + @oy = @height / 2 - light[3].to_i / s_zoom + + # 色相変換 + if ( (hue = light[4].to_i) != 0) + @bitmap.hue_change(hue) + end + end + end # End def initialize + + # 終了処理 + def dispose + @bitmap.dispose + @bitmap = nil + end + end + +end + +class Game_Event + include HN_Light::LightEvent + alias :_hn_light__setup :setup_page unless method_defined?(:_hn_light__setup) + def setup_page(new_page) + _hn_light__setup(new_page) + check_light() + end +end + +class Game_Player + def light_type + return $game_variables[HN_Light::PLAYER_LIGHT_TYPE] + end +end + +class Game_Follower + def light_type + return 0 if !self.visible? + return $game_variables[HN_Light::FOLLOWER_LIGHT_TYPE] + end +end + +class Game_Map + attr_reader :light_events + + # 灯りイベントリストの更新 + def refresh_lights + @light_events = [] + @events.values.each do |event| + if (event.light_type > 0) + @light_events.push(event) + end + end + end + + alias :_hn_light__setup_events :setup_events unless method_defined?(:_hn_light__setup_events) + def setup_events + _hn_light__setup_events() + refresh_lights() + end + + alias :_hn_light__refresh :refresh unless method_defined?(:_hn_light__refresh) + def refresh + _hn_light__refresh() + refresh_lights() + end +end + +class Sprite_Dark < Sprite + @@base_color = Color.new(255,255,255) + + def initialize(viewport = nil) + super(viewport) + @width = Graphics.width + @height = Graphics.height + + case HN_Light::SIMPLIFY + when 1 + @zoom = 2 + when 2 + @zoom = 4 + else + @zoom = 1 + end + @width /= @zoom + @height /= @zoom + self.zoom_x = @zoom.to_f + self.zoom_y = @zoom.to_f + + self.bitmap = Bitmap.new(@width, @height) + self.bitmap.fill_rect(self.bitmap.rect, @@base_color) + self.blend_type = 2 # ブレンドタイプ(減算) + self.z = 500 + self.visible = false + @light_cache = {} + end + + # 灯りを追加する + def add_light(charactor) + return if charactor.nil? + light_type = charactor.light_type + return if (light_type < 1 or light_type > HN_Light::LIGHTS.size) + unless @light_cache.key?(light_type) + @light_cache[light_type] = HN_Light::Light.new(light_type, @zoom) + end + light = @light_cache[light_type] + + # 画面外の場合は何もしない + if @zoom == 1 + return if (charactor.screen_x < 0 - light.width + light.ox) + return if (charactor.screen_x > @width + light.ox) + return if (charactor.screen_y < 0 - light.height + light.oy) + return if (charactor.screen_y > @height + light.oy) + else + return if (charactor.screen_x < 0 - (light.width + light.ox) * @zoom) + return if (charactor.screen_x > (@width + light.ox) * @zoom) + return if (charactor.screen_y < 0 - (light.height + light.oy) * @zoom) + return if (charactor.screen_y > (@height + light.oy) * @zoom) + end + + # アニメーション判定 + if light.cells > 1 + index = (Graphics.frame_count / 4) % light.cells + rect = Rect.new(index * light.width , 0, light.width, light.height) + else + rect = light.bitmap.rect + end + if @zoom != 1 + p_x = charactor.screen_x / @zoom - light.ox + p_y = (charactor.screen_y - 16) / @zoom - light.oy + else + p_x = charactor.screen_x - light.ox + p_y = charactor.screen_y - 16 - light.oy + end + + # 乗算合成(3) + self.bitmap.blend_blt(p_x, p_y, light.bitmap, rect, 3) + end + + + def refresh + self.bitmap.fill_rect(self.bitmap.rect, @@base_color) + $game_map.light_events.each do |event| + next if HN_Light::LIGHTS[event.light_type - 1].nil? + add_light(event) + end + add_light($game_player) + $game_player.followers.each{|f| add_light(f)} + end + + # 更新 + def update + super + refresh() + end + + #-------------------------------------------------------------------------- + # ● 解放 + #-------------------------------------------------------------------------- + def dispose + self.bitmap.dispose + @light_cache.values.each do |light| + light.dispose + end + super + end +end + + +class Spriteset_Map + # 暗闇スプライトの作成 + def create_dark + @dark_sprite = Sprite_Dark.new(@viewport1) + end + + # 暗闇スプライトの更新 + def update_dark + if (@dark_sprite.visible = $game_switches[HN_Light::DARK_SWITCH]) + # @dark_sprite.update + @dark_sprite.dispose + end + end + + # 暗闇スプライトの破棄 + def dispose_dark + @dark_sprite.dispose + end + + # 初期化 + alias :_dark__initialize :initialize unless private_method_defined?(:_dark__initialize) + def initialize + _dark__initialize() + create_dark() + update_dark() + end + + # 更新 + alias :_dark__update :update unless method_defined?(:_dark__update) + def update + _dark__update() + update_dark() if !@dark_sprite.nil? and !@dark_sprite.disposed? + end + + # 終了処理 + alias :_dark__dispose :dispose unless method_defined?(:_dark__dispose) + def dispose + dispose_dark() + _dark__dispose() + end +end diff --git a/Kawariki-patches/ports/message_npi.rb b/Kawariki-patches/ports/message_npi.rb new file mode 100644 index 0000000..e5dd668 --- /dev/null +++ b/Kawariki-patches/ports/message_npi.rb @@ -0,0 +1,1092 @@ +=begin +#_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ + + メッセージテキストデータベース by 村人A + +#_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ + + +#============================================================================== +# バージョン管理 +#============================================================================== + +20/06/01 バージョン2.0 + メッセージウィンドウの背景と位置が連番の際に反映されない不具合を修正し + ました。 + +20/04/01 バージョン1.99 + csvの読み込みエンコードをShift JISからUTF8に変更しました + +20/03/07 バージョン1.98 + 任意の数の言語に対応できるようにしました。 + +20/02/08 バージョン1.97 + 回帰エラーを修正 + エラーが発生しやすい箇所を修正しました。 + +20/01/09 バージョン1.967 + セーブデータ読み込み時に発生するエラー落ちを修正しました。 + +19/12/27 バージョン1.965 + BlueRedZone様のイベント呼び出しの際などメッセージイベントの間に場所移 + 動があった場合に発生する不具合の修正しました。 + ログシートに半角カンマがあった際に文章両端にダブルクォーテーションが表 + 示される不具合を修正しました。 + +19/12/16 バージョン1.961 + BlueRedZone様のイベント呼び出しスクリプトとの競合を解消しました。 + +19/12/12 バージョン1.96 + 過去の不具合の修正をした箇所で抜けがあった箇所を修正しました。 + それにつき、19/11/29に修正したバージョンを1.94から1.945としました。 + +19/12/05 バージョン1.95 + イベントデータベースとコモンデータベースのテキスト番号に巨大数がふられ + ていた場合にも対応できるように修正しました。 + +19/11/29 バージョン1.945 + BRZ様のマップイベント呼び出し時に連番メッセージ取得をした際に発生する + 不具合を修正 + +19/10/29 バージョン1.94 + 場所移動後のデータベーステキスト表示の際に発生する不具合を修正 + +19/10/04 バージョン1.938 + 制御文字記述した際にログデータが取得できない不具合を修正 + +19/10/03 バージョン1.935 + 選択肢拡張の際に空白が出来てしまう競合を解消しました。 + +19/09/27 バージョン1.93 + 選択肢内にログデータベースの文章を入れた際に変数の制御文字が入っていた + 場合、置換するようにしました。 + +19/09/12 バージョン1.92 + BlueRedZone様のスクリプトとの競合解消部分の不具合を修正 + +19/09/03 バージョン1.91 + コモンイベントを含んだデータベーステキストを選択肢前に置くと、テキストの + 順番が前後する不具合を修正 + +19/08/22 バージョン1.9 + 変数にログデータベースのテキストを代入できるスクリプトを作りました。 + +19/08/09 バージョン1.84 + コモンイベントに係る不具合を修正 + +19/08/07 バージョン1.83 + 選択肢や数値入力の処理があった場合に、メッセージが表示されない不具合を + 修正 + +19/08/03 バージョン1.82 + メッセージが挿入されてしまう不具合を修正 + +19/07/31 バージョン1.81 + 連番表示後に場所移動があった際に発生するエラーを修正 + ログテキストの追加に不備があったものを修正 + +19/07/30 バージョン1.8 + 確認されている不具合を修正 + アルゴリズムの変更 + +19/07/22 バージョン1.77 + 確認されている不具合を修正 + +19/07/11 バージョン1.76 + 確認されている不具合を修正 + +19/06/27 バージョン1.75 + イベントコマンドやコモンイベントが上手く流れない不具合を修正しました。 + +19/06/24 バージョン1.7 + メッセージデータベースの1行目にスクリプトの記述があった場合、メッセー + ジ前にそのスクリプトの内容を実行するようにしました。 + +19/05/25 バージョン1.6 + コモンイベントの改行が正しく表示されない不具合を修正 + 例外発生時にコンソールとメッセージに出力するようにしました。 + +19/05/22 バージョン1.5 + 自動実行や並列実行のコモンイベントがあった場合にエラー落ちする不具合を + 修正しました。 + +19/05/19 バージョン1.4 + 半角ハイフンを入れることで連番でデータベーステキストを表示できるように + しました。 + ログウィンドウのIDを数値だけでなく英数記号に対応するようにしました。 + IDに半角ハイフン-を入れるのは連番表示と被ってしまうのでお控えください。 + +19/05/13 バージョン1.3 + イベントページ数による不具合を修正 + マップイベント呼び出しとの競合部分を修正 + +19/05/11 バージョン1.2 + ページ読み込み時の不具合を修正 + スクリプトデータがないセーブで正常に動作するようにしました。 + +19/05/07 バージョン1.1 + コモンイベントのデータベースにも対応するようにしました。 + イベントについて2桁以上のIDで発生する不具合を修正しました。 + csvファイルはSystem/TextCsvフォルダ内に保存するようにしてください。 + +19/05/03 バージョン1.0 + 試作品リリース + + +#============================================================================== +# ヘルプ +#============================================================================== + + +CSVファイルより表示するテキストデータを取得し、文章の表示を行うスクリプトです。 + +2019/5/20追加 +注意として、コモンイベントの自動実行に関わるスクリプト素材とは競合する可能性があ +るので使用する場合はこのスクリプトよりも下に配置するようにしてください。 + + +======================================== + +19/6/24 追記 +☆スクリプトの記述の仕方について + +======================================== + +現在データベースのスクリプト欄では変数操作、スイッチ操作、コモンイベント実行の +3つイベントが処理できるように設定されています。 + +●変数の場合 + +代入の場合 + +$v[変数ID] = 値 + +と記述します。例えば変数3に91を代入したい場合は + +$v[3]=91 + +となります。 +加減乗除,剰余,べき乗についてはそれぞれ + +$v[3] += 91 +$v[3] -= 91 +$v[3] *= 91 +$v[3] /= 91 +$v[3] %= 91 +$v[3] **= 91 + +と記述します。 + + +●スイッチの場合 + +$s[スイッチID] = trueまたはfalse + +と記述します。trueでon、falseでoffとなります。 + +例えばスイッチ1をonにしたい場合 + +$s[1]=true + +となります。 + + +●コモンイベントの場合 + +$c(コモンイベントのID) + +と記述します。こちらは変数やスイッチとは違い、角括弧ではなく、丸括弧であることに +注意してください。 + +例えばコモンイベント31を実行したい場合 + +$c(31) + +となります。 + + +======================================== + +☆変数にログデータベースのテキストを代入するには *8/22追記 + +======================================== + +変数にログデータベースのテキストを代入するスクリプトのコマンドを追加しました。 + +val_in_database(変数ID, ログデータベースのID) + +と記述することで指定した変数にログデータベースのテキストを代入することが出来ます。 +例えば変数3にログデータベース4のテキストを代入する場合は + +val_in_database(3, 4) + +となります。 + + +======================================== + +☆データベースへの文章の記述について、ご注意いただきたい点 + +======================================== + +csvデータテキストをツクールVXAce内で使用する文字列に変換するため、文章の記述法 +にいくつかご注意していただきたい点がございます。 + +・文章に改行を入れる場合は改行を入れる箇所に半角スラッシュ「/」を入れてください。 + +・文章中の半角カンマ「,」の前には半角円マーク「\」を入れてください。 + +・変数の\N[1]や文字色の\C[1]などは通常通りに記述してください。これは他スクリプト +素材で使用する特殊文字についても同様です。 + + +======================================== + +☆イベントのメッセージテキストに使用するデータベース記述の仕方 + +======================================== + +・イベントコマンド「文章の表示」を記述する個所で + +_event_data_base_text_ + テキストID + +と記述してください。例えばそのイベントのテキスト3の取得したい場合は + +_event_data_base_text_3 + +と記述してください。 + +・こちらのデータベースはマップID、イベントID,ページ、テキスト番号で管理され +ます。 + +・セルAにはテキストの場合は「$text+番号」、ページの場合は「$page+ページ数」、 +イベントの場合は「$event+イベントID」、マップの場合は「$map+マップID」 +というように記述されます。 + +・csvデータについて、テキストは上部セルにあるページ数に、ページは上部セルにある +イベントIDに、イベントは上部セルにあるマップIDに所属します。 + +・マップ上イベントに記述することでそのイベントがあるマップID、イベントID、 +スクリプトが書かれている現在のページ数が取得されます。このため、あるイベントから +他のイベントのデータベースに書かれているテキストを取得することは出来ません。 + +・もしイベントに縛られないテキストを取得した場合は + +_log_database_text_ + ログ番号 + +と記述し、ログウィンドウのデータベースから取得してください。 + +・同じ行に英語と日本語のテキストを書き、B列に日本語を、C列に英語のテキストを +書いてください。 + + + +======================================== + +☆ログウィンドウに使用するデータベース記述の仕方 + +======================================== + +・ログウィンドウにてログデータベースのテキストを参照する場合はイベントスクリプト +にて + +set_mlog("_log_database_text_+テキスト番号") + +と記述してください。例えば番号が4のログテキストを表示する場合は + +set_mlog("_log_database_text_4") + +となります。 + +・A列にはログの番号が記述されます。半角数字で記述してください。 + +・同じ行に日本語と英語のテキストを書き、B列に日本語を、C列に英語のテキストを +書いてください。 + +・ログテキストはメッセージウィンドウでも上記_log_database_text_+テキスト番号 +と書くことでログデータベースに書かれたテキストを表示することが可能です。 + + +======================================== + +☆CSVファイルの保存方法 + +======================================== + +・csvについてはイベントのメッセージに使用するものについては「sheet.csv」という +名前で、ログウィンドウに使用するものについては「logSheet.csv」という名前で保存 +してください。 + +・csvの編集についてはExcelで色分けしたもので編集し、出来次第csvに変換するという +手順をお勧めします。ファイルがある場合は上書き保存してください。 +csvではセルの大きさや色分けが保存されないためです。TechnoBrake様にとって一番良き +ように操作してください。 +今回こちらが編集に使ったExcelファイルも同梱しましたので、よろしければご参照くだ +さい。 + + +======================================== + +☆その他ご注意いただきたい点 + +======================================== + +・現在このスクリプトはイベントコマンドで行う「文章の表示」と「ログウィンドウの +スクリプトでの表示」のみに対応しています。 + +・日本語と英語の切り替えには変数を使用しています。現在は変数が0の時日本語、1の時 +英語となります。今後もし、英語以外の言語でも表示したいとなった場合に拡張性を持た +せるために変数を使用しました。 +使用する変数のIDは下記MessageTextDataBaseにて変更可能です。 + + + +何かご不明な点等ございましたらお気兼ねなくメールでご質問ください。 + +=end + +module MessageTextDataBase + #言語を設定する変数のIDを指定します。 + #変数が0の時日本語、1の時英語となります。 + LANG_VARIABLE_ID = 1 + end + + + #============================================================================ + # + # 以下変更不要 + # + #============================================================================ + + class << DataManager + attr_accessor :ms_database + attr_accessor :log_database + attr_accessor :common_database + + + + def convert_ms_csv_data(file_name) + fn = "System/TextCsv/#{file_name}.csv" + res = "" + + # Check if the file exists + unless File.exist?(fn) + puts "Error: File not found - #{fn}" + puts "pls rename #{fn} for the game" + return nil + end + + # Read the file content + begin + # puts "DEBUG start" + puts Dir.pwd + File.open(fn, mode = "r") do |f| + res = f.read + end + rescue Errno::ENOENT + puts "Error: File not found - #{fn}" + puts "pls rename #{fn} for the game" + return nil + end + + # Process the CSV data + res.gsub!(/\\,/, "\&\%") + res.gsub!(/(\/\n)/, "\&\#") + res.gsub!(/(\"\")/, "\&\$") + + # Split the data into lines + rows = res.split("\n") + + # Initialize an array to hold processed rows + processed_rows = [] + + # Iterate over each row and split into columns + rows.each do |row| + # Split the row by commas and replace placeholders back to commas + processed_row = row.split(',').map { |str| str.gsub(/\&\%/, ",") } + processed_rows << processed_row + end + + # Remove the header row (first row) + processed_rows.shift if processed_rows.any? + + return processed_rows + end + + + def create_common_database_text + @common_database = {} + common_res = convert_ms_csv_data('commonSheet') + common_id = -1 + common_res.each{|arr| + identifier = arr[0] + next unless identifier + if identifier.match(/\$common(\d+)/) + common_id = $1.to_i + elsif identifier.match(/\$text(\d+)/) + text_id = $1.to_i + @common_database[common_id] = {} unless @common_database[common_id] + arr.shift + arr.map!{|str| str ? str : ""} + @common_database[common_id][text_id] = arr.dup + end + } + end + + def create_log_database_text + @log_database = {} + + log_res = convert_ms_csv_data('logSheet') + + # Check if log_res is nil or empty + if log_res.nil? || log_res.empty? + puts "Error: logSheet.csv is missing or empty" + return + end + + n = log_res.length - 1 + for i in 0..n + num = log_res[i].shift + log_res[i].map! { |str| str ? str : "" } + @log_database[num] = log_res[i].dup + end + end + + + def create_event_database_text + res = convert_ms_csv_data('sheet') + @ms_database = {} + map_id = event_id = page = text_id = -1 + res.each{|arr| + identifier = arr[0] + next unless identifier + if identifier.match(/\$map(\d+)/) + map_id = $1.to_i + elsif identifier.match(/\$event(\d+)/) + event_id = $1.to_i + elsif identifier.match(/\$page(\d+)/) + page = $1.to_i + elsif identifier.match(/\$text(\d+)/) + text_id = $1.to_i + @ms_database[map_id] = {} unless @ms_database[map_id] + @ms_database[map_id][event_id] = {} unless @ms_database[map_id][event_id] + @ms_database[map_id][event_id][page] = {} unless @ms_database[map_id][event_id][page] + @ms_database[map_id][event_id][page][text_id] = {} unless @ms_database[map_id][event_id][page][text_id] + arr.shift + arr.map!{|str| str ? str : ""} + @ms_database[map_id][event_id][page][text_id] = arr.dup + end + } + end + + + alias _msdate_load_normal_database load_normal_database + def load_normal_database + _msdate_load_normal_database + create_common_database_text + create_log_database_text + create_event_database_text + end + end + #============================================================================== + # ■ Scene_Map + #============================================================================== + class Scene_Map < Scene_Base + attr_reader :message_window + end + + #============================================================================== + # ■ Scene_Load + #------------------------------------------------------------------------------ + #  ロード画面の処理を行うクラスです。 + #============================================================================== + + class Scene_Load < Scene_File + #-------------------------------------------------------------------------- + # ● ロード成功時の処理 + #-------------------------------------------------------------------------- + alias _msdata_on_load_success on_load_success + private :_msdata_on_load_success + def on_load_success + _msdata_on_load_success + $game_system.map_log_text_array = [] if $game_system.map_log_text_array.nil? + #$game_system.logwindow_message = [] if $game_system.logwindow_message.nil? + end + end + + #============================================================================== + # ■ Window_Message + #============================================================================== + + class Window_Message < Window_Base + #-------------------------------------------------------------------------- + # ● 改ページ処理 + #-------------------------------------------------------------------------- + alias _msdata_new_page new_page + def new_page(text, pos) + tex = text.split("\f")[0] + if tex + if tex.match(/_msdate_common\((.+)\)/) + $game_message._interpreter.set_common_event($1.to_i) + end + if tex.match(/_msdate_switch\((.+)\)/) + res_arr = $1.split(",") + $game_switches[res_arr[0].to_i] = res_arr[1] == "true" + end + if tex.match(/_msdate_variable\((.+)\)/) + res_arr = $1.split(",") + $game_variables[res_arr[0].to_i] = res_arr[1].to_i + end + end + _msdata_new_page(text, pos) + end + end + #============================================================================== + # ■ Window_ChoiceList + #============================================================================== + + class Window_ChoiceList < Window_Command + #-------------------------------------------------------------------------- + # ● 選択肢の最大幅を取得 + #-------------------------------------------------------------------------- + def max_choice_width + arr = $game_message.choices.map{|choice| convert_escape_characters(choice)} + arr.collect {|s| text_size(s).width }.max + end + end + + #============================================================================== + # ■ Game_Interpreter + #============================================================================== + + class Game_Interpreter + attr_accessor :is_common + attr_accessor :in_common + + #-------------------------------------------------------------------------- + # ● イベントのセットアップ + #-------------------------------------------------------------------------- + alias _msdata_setup setup + private :_msdata_setup + def setup(list, event_id = 0) + _msdata_setup(list, event_id) + if get_character(0) + @call_event = get_character(0) + page_num = @call_event.page_number + @database_text_map_id_array = [$game_map.map_id, @event_id, page_num] + end + end + #-------------------------------------------------------------------------- + # ● 文章の表示 + #-------------------------------------------------------------------------- + alias _msdata_command_101 command_101 + private :_msdata_command_101 + def command_101 + set_event_info_array + $game_message._interpreter = self + $game_message.background = @params[2] + $game_message.position = @params[3] + text = check_msdata + if text + after_message_common + #20/06/01 修正 + $game_message.background = @params[2] + $game_message.position = @params[3] + $game_message.add(text.join("\n")) + after_message_code_pros + wait_for_message + else + _msdata_command_101 + end + end + + #-------------------------------------------------------------------------- + # ● 場所移動 + #-------------------------------------------------------------------------- + alias _msdata_command_201 command_201 + private :_msdata_command_201 + def command_201 + #$game_map.another_map_event_list = @list#get_character(0).list + _msdata_command_201 + end + + #-------------------------------------------------------------------------- + # ● データベース用メッセージ + #-------------------------------------------------------------------------- + def after_message_code_pros + case next_event_code + when 102 # 選択肢の表示 + @index += 1 + setup_choices(@list[@index].parameters) + when 103 # 数値入力の処理 + @index += 1 + setup_num_input(@list[@index].parameters) + when 104 # アイテム選択の処理 + @index += 1 + setup_item_choice(@list[@index].parameters) + end + end + + #-------------------------------------------------------------------------- + # ● データベース用メッセージ + #-------------------------------------------------------------------------- + def check_msdata + arr = ["_event_data_base_text_", "_common_database_text_", "_log_database_text_"] + func = ["convert_event_database_one_text", "convert_common_database_one_text", "convert_log_database_one_text"] + for i in 0..arr.length-1 + text = check_msdata_pros(arr[i], func[i]) + return text if text + end + return false + end + + #-------------------------------------------------------------------------- + # ● データベース用メッセージ処理 + #-------------------------------------------------------------------------- + def check_msdata_pros(data_text, func_name) + return false unless @list[@index+1].parameters[0].match(/#{data_text}(.+)/) + id = $1 + if id.match(/(\d+)-(\d+)/) + start_index = $1.to_i + n1 = $1.to_i+1 + n2 = $2.to_i + for i in n1-1..n2-1 + text = $game_message.send(func_name, i.to_i) + set_common_event($game_temp.instant_common_event_id) if $game_temp.instant_common_event_id + $game_temp.instant_common_event_id = nil + #20/06/01 修正 + $game_message.background = @params[2] + $game_message.position = @params[3] + $game_message.add(text.join("\n")) + wait_for_message + end + text = $game_message.send(func_name, n2.to_i) + return text + else + num = data_text == "_log_database_text_" ? id : id.to_i + text = $game_message.send(func_name, num) + @index += 1 + end + return text + end + + #-------------------------------------------------------------------------- + # ● 選択肢のセットアップ + #-------------------------------------------------------------------------- + alias _msdata_setup_choices setup_choices + private :_msdata_setup_choices + def setup_choices(params) + return @msdata_added_ms_command = false if @msdata_added_ms_command == "choice" + _msdata_setup_choices(params) + end + + #-------------------------------------------------------------------------- + # ● 数値入力のセットアップ + #-------------------------------------------------------------------------- + alias _msdata_setup_num_input setup_num_input + private :_msdata_setup_num_input + def setup_num_input(params) + return @msdata_added_ms_command = false if @msdata_added_ms_command == "number" + _msdata_setup_num_input(params) + end + + #-------------------------------------------------------------------------- + # ● アイテム選択のセットアップ + #-------------------------------------------------------------------------- + alias _msdata_setup_item_choice setup_item_choice + private :_msdata_setup_item_choice + def setup_item_choice(params) + return @msdata_added_ms_command = false if @msdata_added_ms_command == "item" + _msdata_setup_item_choice(params) + end + #-------------------------------------------------------------------------- + # ● メッセージデータベースを使用するための情報を設定 + #-------------------------------------------------------------------------- + def set_event_info_array + map_id = event_id = page_num = nil + set_map_id_array + a = @database_text_map_id_array || [nil,nil,nil] + $game_message.get_event_info(a[0], a[1], a[2], @in_common) + end + + #-------------------------------------------------------------------------- + # ● メッセージ追加後即時コモンイベント + #-------------------------------------------------------------------------- + def set_map_id_array + return unless @database_text_map_id_array.nil? + return if @in_common + info = $game_system.call_map_script_info + if info + @database_text_map_id_array = info.dup + else + if get_character(0) + page_num = get_character(0).page_number + @database_text_map_id_array = [$game_map.map_id, @event_id, page_num] + end + end + $game_system.call_map_script_info = nil + end + + #-------------------------------------------------------------------------- + # ● メッセージ追加後即時コモンイベント + #-------------------------------------------------------------------------- + def after_message_common + return unless $game_temp.instant_common_event_id + id = $game_temp.instant_common_event_id + $game_temp.instant_common_event_id = nil + set_common_event(id) + end + + #-------------------------------------------------------------------------- + # ● コモンイベント + #-------------------------------------------------------------------------- + def command_117 + set_common_event(@params[0]) + end + + #-------------------------------------------------------------------------- + # ● コモンイベントのセット + #-------------------------------------------------------------------------- + def set_common_event(id) + common_event = $data_common_events[id] + pre_info = [$game_message.background, $game_message.position] + if common_event + child = Game_Interpreter.new(@depth + 1) + child.setup(common_event.list, same_map? ? @event_id : 0) + child.is_common = true + child.in_common = id + child.run + end + $game_message.background = pre_info[0] + $game_message.position = pre_info[1] + end + + #-------------------------------------------------------------------------- + # ● イベントコマンドの実行 + #-------------------------------------------------------------------------- + alias _msdata_execute_command execute_command + private :_msdata_execute_command + def execute_command + _msdata_execute_command + command = @list[@index] +#~ puts command + if command != nil && command.code == 0 && !@is_common && @indent == 0 && @index == @list.size - 1 + end_pross + end + end + + #-------------------------------------------------------------------------- + # ● リストの復元 + #-------------------------------------------------------------------------- + def restore_list + if @pre_list + if @is_common + $data_common_events[@in_common].list = @pre_list + else + $game_map.events[@event_id].reset_list(@pre_list) + #@save_character_event.reset_list(@pre_list) + end + @list = @pre_list + @index -= @msdata_back_index_num + @msdata_add_pros_ms = false + end + @pre_list = nil + end + + #-------------------------------------------------------------------------- + # ● 終了処理 + #-------------------------------------------------------------------------- + def end_pross + @in_common = nil + @database_text_map_id_array = nil + end + + #-------------------------------------------------------------------------- + # ● 変数にデータベースの情報を代入 + #-------------------------------------------------------------------------- + def val_in_database(val, id) + puts MessageTextDataBase::LANG_VARIABLE_ID.to_s + begin + lang = $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + text = DataManager.log_database.dup[id.to_s][lang] + rescue + puts "ログデータベースにデータが見つかりません。ID:" + id.to_s + " 言語番号:" + lang.to_s + return + end + text = $game_message.convert_app_text(text)[0] + raise("戦闘中の代入は実装しておりません。") if $game_party.in_battle + $game_variables[val] = SceneManager.scene.message_window.convert_escape_characters(text) + end + + def var_from_sheet(var, map, event, page, text, line) + raw = DataManager.ms_database[map][event][page][text][ $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + 1 ] + convert = $game_message.convert_app_text(raw)[line] + $game_variables[var] = SceneManager.scene.message_window.convert_escape_characters(convert) + end + + def var_from_common(var, common, text, line) + raw = DataManager.common_database[common][text][ $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + 1 ] + convert = $game_message.convert_app_text(raw)[line] + $game_variables[var] = SceneManager.scene.message_window.convert_escape_characters(convert) + end + end + #============================================================================== + # ■ Game_Temp + #============================================================================== + + class Game_Temp + attr_accessor :instant_common_event_id # コモンイベント ID + def instant_exec_common(id) + @instant_common_event_id = id + end + end + + #============================================================================== + # ■ Game_Map + #============================================================================== + + class Game_Map + attr_accessor :another_map_event_list + #-------------------------------------------------------------------------- + # ● 自動実行のコモンイベントを検出/セットアップ + #-------------------------------------------------------------------------- + alias _msdata_setup_autorun_common_event setup_autorun_common_event + private :_msdata_setup_autorun_common_event + def setup_autorun_common_event + event = _msdata_setup_autorun_common_event + if event + @interpreter.setup(event.list) + @interpreter.in_common = event.id + end + event + end + end + + #============================================================================== + # ■ Game_CommonEvent + #============================================================================== + + class Game_CommonEvent + attr_accessor :common_id + #-------------------------------------------------------------------------- + # ● オブジェクト初期化 + #-------------------------------------------------------------------------- + alias _msdata_initialize initialize + private :_msdata_initialize + def initialize(common_event_id) + _msdata_initialize(common_event_id) + @common_id = common_event_id + end + #-------------------------------------------------------------------------- + # ● リフレッシュ + #-------------------------------------------------------------------------- + alias _msdata_refresh refresh + private :_msdata_refresh + def refresh + _msdata_refresh + if active? + @interpreter.in_common = @common_id + end + end + end + + #============================================================================== + # ■ Game_System + #============================================================================== + + class Game_System + #-------------------------------------------------------------------------- + # ● 公開インスタンス変数 + #-------------------------------------------------------------------------- + attr_accessor :call_map_script_info # 追加するログのメッセージ + end + + #============================================================================== + # ■ Game_Event + #============================================================================== + + class Game_Event < Game_Character + attr_reader :page_number + #-------------------------------------------------------------------------- + # ● リフレッシュ + #-------------------------------------------------------------------------- + alias _msdata_refresh refresh + def refresh + _msdata_refresh + if @event.pages.reverse.index(@page) + @page_number = @event.pages.index(@page) + 1 + end + end + + #-------------------------------------------------------------------------- + # ● イベントリストの復元 + #-------------------------------------------------------------------------- + def reset_list(list) + @list = list + end + end + + + #============================================================================== + # ■ Game_Message + #============================================================================== + + class Game_Message + attr_accessor :pre_md_message_info + attr_accessor :_interpreter + + #-------------------------------------------------------------------------- + # ● クリア + #-------------------------------------------------------------------------- + alias _msdata_clear clear + def clear + @call_map_script_info = "" + _msdata_clear + end + + #-------------------------------------------------------------------------- + # ● メッセージが表示された時のマップイベント情報を取得 + #-------------------------------------------------------------------------- + def get_event_info(map_id, id, page, is_common) + @msd_map_id = map_id + @msd_event_id = id + @msd_event_page = page + @common_id = is_common + end + + #-------------------------------------------------------------------------- + # ● その他のテキストだった場合その他のデータベースより取得 + #-------------------------------------------------------------------------- + def convert_other_database_text(text) + m1 = text.match(/_log_database_text_(.+)/) + return false unless m1 + log_num = m1[1] + if log_num.match(/(\d+)-(\d+)/) + series_text = [] + for i in $1.to_i..$2.to_i + text = check_log_database_text(i.to_s) + series_text.push(text) + if i != $2.to_i + series_text[series_text.length-1] += "\f" + end + end + return series_text + else + text = check_log_database_text(log_num) + return convert_app_text(text) + end + end + #-------------------------------------------------------------------------- + # ● ログテキストの取得と存在確認 + #-------------------------------------------------------------------------- + def check_log_database_text(ind) + lang = $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + data = DataManager.log_database[ind] + unless data + tex = "ログテキストデータが不正かデータがありません。\n" + tex += "番号:" + ind.to_s + puts tex + return tex + end + return data[lang] + end + #-------------------------------------------------------------------------- + # ● 単一のメッセージ用のテキストだった場合メッセージデータベースより取得 + #-------------------------------------------------------------------------- + def convert_event_database_one_text(t_index) + lang = $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + 1 + begin + script_str = DataManager.ms_database[@msd_map_id][@msd_event_id][@msd_event_page][t_index][0].dup + pre_script_check(script_str) + text = DataManager.ms_database[@msd_map_id][@msd_event_id][@msd_event_page][t_index][lang] + res_arr = convert_app_text(text) + rescue + tex = "イベントテキストデータが不正かデータがありません。\n" + tex += "マップID:" + @msd_map_id.to_s + " イベントID:" + @msd_event_id.to_s + " ページ:" + @msd_event_page.to_s + " テキストID:" + t_index.to_s + " 言語番号:" + lang.to_s + puts tex + return [tex] + end + return res_arr + end + #-------------------------------------------------------------------------- + # ● 単一のメッセージ用のテキストだった場合メッセージデータベースより取得 + #-------------------------------------------------------------------------- + def convert_common_database_one_text(ind) + lang = $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + 1 + begin + data = DataManager.common_database[@common_id][ind] + script_str = data[0].dup + pre_script_check(script_str) + text = DataManager.common_database[@common_id][ind][lang] + res_arr = convert_app_text(text) + rescue + tex = "コモンIDか番号が不正か、データがありません。\n" + tex += "コモンID:" + @common_id.to_s + " 番号:" + ind.to_s + " 言語:" + lang.to_s + puts tex + return [tex] + end + return res_arr + end + #-------------------------------------------------------------------------- + # ● ログテキストの取得と存在確認 + #-------------------------------------------------------------------------- + def convert_log_database_one_text(ind) + lang = $game_variables[MessageTextDataBase::LANG_VARIABLE_ID] + data = DataManager.log_database[ind] + unless data + tex = "ログテキストデータが不正かデータがありません。\n" + tex += "番号:" + ind.to_s + puts tex + return [tex] + end + return puts "変数:" + lang.to_s + "に対応する言語のデータがありません。" unless data[lang] + return convert_app_text(data[lang].dup) + 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 + #-------------------------------------------------------------------------- + # ● コモンテキストの取得と存在確認 + #-------------------------------------------------------------------------- + def pre_script_check(script_str) + #2つ以上のコモンがある場合は考えない + has_common = !script_str.index("$c").nil? + $game_temp.instant_common_event_id = nil + @controll_switch_val = [] + @controll_variables_val = [] + script_str.gsub!(/\$v/){|w| "$game_variables"} + script_str.gsub!(/\$s/){|w| "$game_switches"} + script_str.gsub!(/\$c/){|w| "$game_temp.instant_exec_common"} + script_str_arr = convert_app_text(script_str) + script_str_arr.each{|str| eval(str)} + return has_common + end + + #-------------------------------------------------------------------------- + # ● テキストの最終コンバート + #-------------------------------------------------------------------------- + def convert_app_text(text) + text = text || "" + return [""] if text == "" + mch1 = text.match(/\"(.+)/) + mch2 = mch1[1].match(/(.+)\"/) if mch1 + text = mch2 ? mch2[1] : mch1 ? mch1[1] : text + text.gsub!(/(\&\$)/, "\"") + arr = text.split("&#") + return arr + end + end diff --git a/Kawariki-patches/ports/testencode.rb b/Kawariki-patches/ports/testencode.rb new file mode 100644 index 0000000..792592f --- /dev/null +++ b/Kawariki-patches/ports/testencode.rb @@ -0,0 +1,1080 @@ +# =========================================================================== +# ★ WF-RGSS Scripts ★ +# 共通スクリプト(XP/VX/VXAce両対応版) +# バージョン : rev-29 (2013-3-26) +# 作者 : A Crying Minister (WHITE-FLUTE) +# サポート先URI: http://www.whiteflute.org/wfrgss/ +# --------------------------------------------------------------------------- +# 機能: +# ・このセクションより後ろのスクリプトでSyntaxError が発生しても +# 別の例外にすりかえられるため、詳細情報が削除されなくなります。 +# (VXAceでは標準で削除されなくなりました。) +# ・SyntaxErrorの情報をファイルに書き出します。 +# ・不用意なデバッグモードへの遷移を防止します。 +# (元からデバッグモードの場合は問題ありません。) +# ・不正なデバッグモードへの遷移を阻害します。 +# ・デバッグコンソールを表示しデバッグしやすくします。 +# --------------------------------------------------------------------------- +# 影響: +# ・外部コマンド実行など一部の組み込み関数が未定義になります。 +# ・例外 SyntaxError は作成できません。(XP/VX) +# (※ ScriptSyntaxError にすりかえられます。) +# --------------------------------------------------------------------------- +# 設置場所:一番最初のセクション(Game_Temp(XP)/モジュール(VX/VXAce)よりも前) +# 必要スクリプト: +# ・共通実行スクリプト(VXAce は、Exit-EXで置き換える事ができます。) +# 注意事項: +# 共通実行スクリプトをMainセクションに上書きして利用することを推奨します。 +# =========================================================================== +BEGIN { + + module WFRGSS +# ------------------------------------------------------------------------- +# ● 調整ポイント +# ------------------------------------------------------------------------- +# デバッグモード遷移を阻害するかを設定します。 +# 確実に保護できるわけではありませんので過信は禁物です。 + +DEBUG_CHECK = true + +# ------------------------------------------------------------------------- +# デバッグモード時、デバッグコンソールを表示します。 +# (VXAceでは設定に関わらず標準装備されています。) + +DEBUG_CONSOLE = false + +# ------------------------------------------------------------------------- +# リリースモード時、デバッグメッセージを消去します。 + +DELETE_DEBUG_MESSAGE = false + +# ------------------------------------------------------------------------- +# XPのみ。 デバッグフラグをVX/VXAce準拠に修正するか? +# (!)スクリプト対応が必要となります。 + +DEBUG_FLAG_CHANGE = false + + +# ------------------------------------------------------------------------- +# 調整ポイント終わり +# ------------------------------------------------------------------------- +end + + + +# --------------------------------------------------------------------------- +# ● 例外定義 +# --------------------------------------------------------------------------- + +# 継承例外 +class InheritanceError < Exception;end + +# Mix-in 例外 +class IncludeError < Exception;end + +# 仮想クラス、メソッドの使用 +class AbstractError < NotImplementedError;end + +# ◆ バグ検出 +# バグ以外、絶対にありえない状態になったときに発生させてください。 +# ※ バグ報告を行うためのものなので途中で破棄してはいけません。 +class BugDetected < Exception;end + +# ◆ 内部バグ報告用 +# これは、WFRGSS でバグしかありえない状態に陥ったときに発生します。 +# ※ バグ報告を行うためのものなので途中で破棄してはいけません。 +class InternalBugDetected < BugDetected;end + +# イベント階層が深すぎる場合 +class EventDeepError < SystemStackError;end + +# 初期化失敗 +class InitializeError < TypeError;end + +# アサーション失敗 +class AssertionError < Exception;end + +# ◆ 例外 Hangup +# ハングアップしたとみなされたときに投げられる例外。 +# 深い階層からでは捉えられないことがあるため、注意が必要 +# RPGVX/VXAceにはありませんが、XPとの互換性を保つため、設定しています。 +class Hangup < Exception;end + +# ◆ 例外 Reset +# Graphics.update の呼び出し時にF12が押されていると投げられる例外。(XP) +# F12 が押されていると投げられる例外(VX) +# これがセクション外に出ると +# 最初のセクションから再び実行をやり直そうとします。 +# この操作は、組み込みクラスのメソッドにて、 +# alias や undef が使われていると +# 不具合の元になります。 +class Reset < Exception;end + +# セキュリティ違反 +class SecurityHazard < Exception;end + +# RPGXP互換性のため +if WFRGSS::DEBUG_FLAG_CHANGE +$TEST = $DEBUG +$DEBUG = false +else +$TEST = false unless $TEST +end + +module WFRGSS +@@debug = ($TEST or $DEBUG) +def self.check +debug = ($TEST or $DEBUG) +if debug != @@debug or (@@debug and + (FileTest.file?("Game.rgssad") or FileTest.file?("Game.rgss2a") or + FileTest.file?("Game.rgss3a"))) +raise(SecurityHazard ,"Insecure - debug flag can't be change.") +end +end +end + +# --------------------------------------------------------------------------- +# ◆◆ デバッグモード阻止対策 +# --------------------------------------------------------------------------- +# デバッグモード防止策 +if defined? untrace_var +undef untrace_var +# デバッグモードでない場合、デバッグモードへの遷移を禁止する +proc = Proc.new { + if $DEBUG + $DEBUG = false + $TEST = false + $BTEST = false + raise(SecurityHazard ,"Insecure - debug flag can't be change.", caller(1)) + end + } +trace_var( :$DEBUG , proc ) +trace_var( :$-d , proc ) # $DEBUG の別名 +proc = nil + +# デバッグモードでない場合、デバッグモードへの遷移を禁止する +proc = Proc.new { + if $TEST + $DEBUG = false + $TEST = false + $BTEST = false + raise(SecurityHazard ,"Insecure - debug flag can't be change.", caller(1)) + end + } +trace_var( :$TEST , proc ) # RPGVX デバッグフラグ +proc = nil + +# 戦闘テストで無い場合、戦闘テストへの遷移を禁止する +proc = Proc.new { + if $BTEST + $DEBUG = false + $TEST = false + $BTEST = false + raise(SecurityHazard ,"Insecure - battle test flag can't be change.", + caller(1)) + end + } +trace_var( :$BTEST , proc ) +proc = nil +end + +unless defined? ScriptSyntaxError +# RGSSでは、本来持っているSyntaxErrorの詳細情報をすべて削除してしまい、 +# デバッグが非常に困難になってしまいます。 +# そのために用意されている例外です。 +# ※特にイベントスクリプト上でSyntaxErrorを出すととても悲惨なことに… +# 「スクリプト実行中に SyntaxError が発生しました。」だけで +# すぐに修正できるのでしょうか? +class ScriptSyntaxError < Exception;end +# ------------------------------------------------------------------------- +# ◆ 組み込み例外クラス SyntaxError +# ------------------------------------------------------------------------- +# RGSSでは、本来持っているSyntaxErrorの詳細情報をすべて削除してしまうため、 +# SyntaxError のコンストラクタを乗っ取って別の例外にすりかえています。 +# ※ SyntaxError のインスタンスは作成できません。 +class SyntaxError < ScriptError +ERROR = Hash.new +end +class << SyntaxError +#-------------------------------------------------------------------------- +# ◆ (!※継承禁止※!) +#-------------------------------------------------------------------------- +def SyntaxError.inherited( subclass ) +raise( InheritanceError , + "can't inherit class #{subclass} from #{self}" , caller(1) ) +end +private_class_method(:inherited) +unless defined? BasicObject +undef allocate +undef new +#------------------------------------------------------------------------ +# ◆ 強制的に別の例外にすりかえる +#------------------------------------------------------------------------ +def new( errors , *args ) +error_message = make_error_message(errors) +error_message_dump(error_message) +ScriptSyntaxError.new(error_message) +end +else +alias __wfrgss__new__ new unless $! +def new( errors , *args ) +error_message = make_error_message(errors) +error_message_dump(error_message) +__wfrgss__new__(errors) +end +end +private +#---------------------------------------------------------------------------- +# ◆(内部専用)◆ エラーメッセージ作成 +#---------------------------------------------------------------------------- +def make_error_message( error_message ) +extra = "" +unless (error_message[/^(?:Section)?{?(\d+)}?:(\d+):/]).nil? +nums = $1.to_i # セクションを拾い出した場合 +extra = $RGSS_SCRIPTS.at(nums).at(1) +unless $2.nil? +lines = $2.to_i +unless ($RGSS_SCRIPTS.at(nums).at(3)).nil? +details = show_details_source_code( nums , lines ) +unless details.empty? +error_message += "\n** script source code:\n#{details}" +else +error_message += +"\n** script source code:#{lines} unknown data.\n" +end +end +end +end +error_message.gsub!(/^(?:Section)?{?(\d+)}?:/){"( #{extra} ):#{$1}:lines "} +error_message +end +#-------------------------------------------------------------------------- +# ◆(内部専用)◆ 情報を書きこみ +#-------------------------------------------------------------------------- +def error_message_dump(error_message) +e = Zlib::crc32(error_message) +return if SyntaxError::ERROR[e] +SyntaxError::ERROR[e] = true +File.open("syntaxerror.txt","a") do | file | +file.write("*SyntaxError - " + + (Time.now).strftime("%Y-%m-%d %H:%M:%S (%A)") + "\n") +file.write( "#{error_message}\n" ) +end +end +#-------------------------------------------------------------------------- +# ◆(内部専用)◆ ソースコード位置読み込み +#-------------------------------------------------------------------------- +def show_details_source_code( nums , lines ) +details = "" +splitstr = ($RGSS_SCRIPTS.at(nums).at(3)).split(/\r\n/) +if lines < 4 +ranges = ( 0...( lines + 3 )) +else +ranges = (( lines - 4 )...( lines + 3 )) +end +for i in ranges +unless (splitstr.at(i)).nil? +if i != (lines - 1) +details += "|" + splitstr.at(i) + "\n" +else +details += ">" + splitstr.at(i) + "\n" +end +else +details += "[End of File]\n" +break +end +end +splitstr = nil +rpgvxace? ? details.force_encoding("UTF-8") : details +end +end + + +# ------------------------------------------------------------------------- +# ◆ RGSS 組み込みモジュール Graphics +# ------------------------------------------------------------------------- + +class << Graphics +#------------------------------------------------------------------------ +# ◆ リセットを無視する Graphics.update +#------------------------------------------------------------------------ +def Graphics.safe_update +begin +update +rescue Reset +# リセット操作を無視する。 +# ただしカーソルアニメーションなどが巻き戻される。 +nil +end +end + +if WFRGSS::DEBUG_CHECK +#------------------------------------------------------------------------ +# ◆ Graphics.update にチェックを加える +#------------------------------------------------------------------------ +alias ___original__update___ update +def Graphics.update +WFRGSS.check +___original__update___ +end +private(:___original__update___) +end +end + +module Graphics +# ----------------------------------------------------------------------- +# ◆ クラス変数 +# ----------------------------------------------------------------------- +@@rate = 0 +@@count = 0 +# ----------------------------------------------------------------------- +# ◆ フレームレートを一時的に変更 +# ----------------------------------------------------------------------- +def self.frame_workset( frame_rates ) +@@rate = Graphics.frame_rate +@@count = Graphics.frame_count +Graphics.frame_rate = frame_rates +end +# ----------------------------------------------------------------------- +# ◆ フレームレートを元に戻す +# ----------------------------------------------------------------------- +def self.frame_restore +cnt = Graphics.frame_count - @@count +cnt *= @@rate +cnt /= Graphics.frame_rate +Graphics.frame_count = @@count + cnt +Graphics.frame_rate = @@rate +end +# ----------------------------------------------------------------------- +# ◆ 解像度横の幅(VX/VXAceでは無視される) +# ----------------------------------------------------------------------- +unless defined? Graphics.width +def self.width +640 +end +end +# ----------------------------------------------------------------------- +# ◆ 解像度縦の幅(VX/VXAceでは無視される) +# ----------------------------------------------------------------------- +unless defined? Graphics.height +def self.height +480 +end +end +end +end + +# --------------------------------------------------------------------------- +# ◆ 組み込みモジュール GC +# --------------------------------------------------------------------------- +module GC +module_function +#-------------------------------------------------------------------------- +# ◆ 安全にガーベージコレクションを行う +#-------------------------------------------------------------------------- +def update_start +Graphics.safe_update +start +Graphics.safe_update +end +end + +} + +unless defined? Abstract + # ----------------------------------------------------------------------------- + # ◆◆ 組み込みクラス Class + # ----------------------------------------------------------------------------- + class Class + # --------------------------------------------------------------------------- + # ◆ クラス変数 + # --------------------------------------------------------------------------- + @@_abstract_class = Hash.new + # --------------------------------------------------------------------------- + # ◆ 抽象クラス追加 + # --------------------------------------------------------------------------- + def self.abstract_class( klass ) + __failure_type_call__( klass ) unless klass.is_a?(Class) + unless @@_abstract_class[klass.__id__] + @@_abstract_class[klass.__id__] = klass + end + end + # --------------------------------------------------------------------------- + # ◆ 抽象クラス判定 + # --------------------------------------------------------------------------- + def self.abstract?( klass ) + @@_abstract_class[klass.__id__] + end + end + + module Abstract + # --------------------------------------------------------------------------- + # ◆ 抽象化 + # --------------------------------------------------------------------------- + def self.included( klass ) + if klass.include?(FinalClass) + # FinalClass モジュールが既にインクルードされている + raise( IncludeError , + "can't include #{klass} from #{self}" , caller(1) ) + end + Class.abstract_class( klass ) + klass.extend(Abstract_Class__) + end + private_class_method(:included) + end + + # --------------------------------------------------------------------------- + # ◆ 抽象化 ※ 特異メソッド専用 ※ + # --------------------------------------------------------------------------- + module Abstract_Class__ + # ------------------------------------------------------------------------- + # ◆ ※ 抽象クラス + # ------------------------------------------------------------------------- + def allocate + return super unless Class.abstract?( self ) + raise( AbstractError , + "class #{self} cannot use it because of the abstraction class.", + caller(1) ) + end + # ------------------------------------------------------------------------- + # ◆ ※ 抽象クラス + # ------------------------------------------------------------------------- + def new( *args ) + return super unless Class.abstract?( self ) + raise( AbstractError , + "class #{self} cannot use it because of the abstraction class.", + caller(1) ) + end + # ------------------------------------------------------------------------- + # ◆ ※ Mix-in 制限 + # ------------------------------------------------------------------------- + def self.included( klass ) + unless self.is_a?(Class) + raise( IncludeError , + "can't include #{klass} from #{self}" , caller(1) ) + end + end + end + + module FinalClass + # --------------------------------------------------------------------------- + # ◆ 継承できないクラス + # --------------------------------------------------------------------------- + def self.included( klass ) + if Class.abstract?( klass ) + # Abstract モジュールが既にインクルードされている + raise( IncludeError , + "can't include #{klass} from #{self}" , caller(1) ) + end + klass.module_eval(<<-End) + def self.inherited( subclass ) + raise( InheritanceError , + sprintf(\"can't inherit class %s from %s\",subclass.inspect, + self.inspect ) , caller(1) ) + end + private_class_method(:inherited) + End + end + private_class_method(:included) + end + + # --------------------------------------------------------------------------- + # ◆ 組み込みモジュール Kernel + # --------------------------------------------------------------------------- + + module Kernel + # --------------------------------------------------------------------------- + # ◆ 危険な関数を未定義に設定 + # --------------------------------------------------------------------------- + # 外部コマンド読み込みなど危険な操作は明示的に禁止にします。 + # ※ これらのコマンドの必要性が(通常は)ないうえ、 + # 危険(システム破壊をもたらすことがある)なため取り外しています。 + # ※ もし、これらを使用する場合は、効果と危険性を考慮してください。 + # --------------------------------------------------------------------------- + # 外部コマンドを実行する。 + undef ` #` # 外部コマンドを実行する。 + undef exec # 外部コマンドを実行する 制御を返さない。 + undef fork # サブシェルを生成する。 + undef system # 外部コマンドを実行する。 + undef abort # 後処理を行わずに強制終了する。後処理が必要な時に困る。 + undef exit! # abort と同じ。 + undef open # コマンドが実行できる。(ファイルを開くならIO.openを用いる) + #-------------------------------------------------------------------------- + private + #-------------------------------------------------------------------------- + # ◆ RPGVX/VXAce かどうか + #-------------------------------------------------------------------------- + def rpgvx? + defined? Graphics.resize_screen + end + #-------------------------------------------------------------------------- + # ◆ RPGVXAce かどうか + #-------------------------------------------------------------------------- + def rpgvxace? + defined? BasicObject + end + #-------------------------------------------------------------------------- + # ◆ RGSSのバージョン + #-------------------------------------------------------------------------- + def rgss_version + rpgvx? ? (rpgvxace? ? 3 : 2 ) : 1 + end + #-------------------------------------------------------------------------- + # ◆ テストモードかどうか + #-------------------------------------------------------------------------- + def debug? + $DEBUG or $TEST + end + #-------------------------------------------------------------------------- + # ◆ デバッグコンソール + #-------------------------------------------------------------------------- + if WFRGSS::DEBUG_CONSOLE and debug? and (not rpgvxace?) + unless $@ + $VERBOSE = true + alias wfrgss_p p + Win32API.new("kernel32","AllocConsole",'v','l').call + $stdout.reopen("conout$","w") + def p(*a) + a.each{|v|$stdout.puts(String.utf82ansi(v.inspect))} + end + end + elsif WFRGSS::DELETE_DEBUG_MESSAGE + unless $@ + alias display_p p + def p(*a) + # 何も出力しない + end + end + end + #-------------------------------------------------------------------------- + # ◆ 例外セット + # 例外を発生させます。 + #-------------------------------------------------------------------------- + # ◆ method_missing と同様 + #-------------------------------------------------------------------------- + def __failure_method_call__( method_name , *args ) + raise( NoMethodError , + "undefined method `#{method_name}' for #{self}" , caller(2) ) + end + #-------------------------------------------------------------------------- + # ◆ オーバーライドが必要 + #-------------------------------------------------------------------------- + def __override_required_call__( method_name , *args ) + raise( AbstractError , + "It is necessary to do override to use method `#{method_name}'" + + " of this class #{self}. " + + "(or, it tried to call the method of a super-class.)" , caller(2)) + end + #-------------------------------------------------------------------------- + # ◆ 例外 TypeErrorを発生させる + #-------------------------------------------------------------------------- + def __failure_type_call__( object_type ) + e = ( object_type.class ).to_s + raise( TypeError , "no implicit conversion from #{e}" , caller(2) ) + end + #-------------------------------------------------------------------------- + # ◆ 例外 RangeErrorを発生させる ( num が Numeric でない場合はBugDetected ) + #-------------------------------------------------------------------------- + def __outof_range_call__( num , str , call_value = 2 ) + if num.is_a?(Numeric) + if str.is_a?(String) + raise( RangeError , "#{num} out of range of #{str}",caller(call_value)) + else + e = ( str.class ).to_s + raise(TypeError,"no implicit conversion from #{e}",caller(call_value)) + end + else + e = ( num.class ).to_s + raise( BugDetected, "[BUG] no implicit conversion from #{e} (TypeError)", + caller(1)) + end + end + #-------------------------------------------------------------------------- + # ◆ 例外 ArgumentErrorを発生させる ( str が String でない場合はBugDetected ) + #-------------------------------------------------------------------------- + def __invalid_value_call__( str , value , call_value = 2 ) + if str.is_a?(String) + raise( ArgumentError , "invalid #{str} for #{value.to_s}") + else + e = ( str.class ).to_s + raise( BugDetected, "[BUG] no implicit conversion from #{e} (TypeError)", + caller(1)) + end + end + #-------------------------------------------------------------------------- + # ◆ 例外 SecurityErrorを発生させる ( str が String でない場合はBugDetected ) + #-------------------------------------------------------------------------- + def __insecure_call__( str ) + if str.is_a?(String) + raise( SecurityError , "Insecure operation - #{str}" , caller(2) ) + else + e = ( str.class ).to_s + raise( BugDetected, "[BUG] no implicit conversion from #{e} (TypeError)", + caller(1)) + end + end + #-------------------------------------------------------------------------- + # ◆ バグを報告する。 ( str が String でない場合も BugDetected ) + #-------------------------------------------------------------------------- + def __report_bug( str ) + if str.is_a?(String) + raise( BugDetected , str , caller(1)) + else + e = ( str.class ).to_s + raise( BugDetected , + "[BUG] no implicit conversion from #{e} (TypeError)" , caller(1) ) + end + end + #-------------------------------------------------------------------------- + # ◆ バグを報告する。(異常な値を検出) + #-------------------------------------------------------------------------- + def __report_bug_invalid_value( value ) + raise( BugDetected , "[BUG] invalid value for #{value}", caller(1)) + end + #-------------------------------------------------------------------------- + # ◆※!※◆ WF-RGSS の バグを報告する。 + # ※ このメソッドは WF-RGSS 内部バグを報告するためのものです。 + # ※ カスタマイズ時にバグを報告したい場合は、 + # __report_bug( str ) を使用してください。 + #-------------------------------------------------------------------------- + def __report_internal_bug( str ) + if str.is_a?(String) + raise( InternalBugDetected , str , caller(1)) + else + e = ( str.class ).to_s + raise( InternalBugDetected , + "[BUG] no implicit conversion from #{e} (TypeError)" , caller(1) ) + end + end + #-------------------------------------------------------------------------- + # ◆ チェック関連メソッド + # 型や範囲をチェックします。 + #-------------------------------------------------------------------------- + # ◆ 型が合わない場合、例外 TypeErrorを発生させる + # (※ klass が異常な場合はBugDetected ) + #-------------------------------------------------------------------------- + def _type_check_( object_type , klass , call_value = 2 ) + begin + return if object_type.is_a?(klass) + e = ( object_type.class ).to_s + raise( TypeError,"no implicit conversion from #{e}") + rescue TypeError => errobj + # 例外の報告位置をすりかえる + if errobj.message[/^no/].nil? + raise( BugDetected , "[BUG] #{errobj.message} (TypeError)",caller(1)) + else + raise( TypeError , errobj.message , caller(call_value)) + end + end + end + #-------------------------------------------------------------------------- + # ◆ Fixnum の範囲を検証する ※範囲が異常なら例外を発生させる + #-------------------------------------------------------------------------- + def _fixnum_range_check( value , minimum , maximum ) + __failure_type_call__( value , 3 ) unless value.is_a?(Fixnum) + unless value >= minimum and value <= maximum + # 範囲外のため、例外を発生させる + call_ary = caller(1) + if call_ary.size > 0 + sections = call_ary.at(0) + str = sections + " ( range #{minimum} .. #{maximum} )" + else + str = "caller method (unknown) ( range #{minimum} .. #{maximum} )" + end + __outof_range_call__( value , str , 3 ) + end + end + #-------------------------------------------------------------------------- + # ◆ Integer の範囲にまとめる (※処理速度が求められる箇所では用いない) + # ※異常範囲が認められていない場合は、↑のメソッドを用いること + #-------------------------------------------------------------------------- + def _integer_range_value( value , minimum , maximum ) + __failure_type_call__( value , 3 ) unless value.is_a?(Integer) + if value < minimum + return minimum + elsif value > maximum + return maximum + else + return value + end + end + # --------------------------------------------------------------------------- + # 可視性を変更 + public + # これ以降はモジュール関数として定義 + module_function + # --------------------------------------------------------------------------- + # ◆ 真 を検証 + # --------------------------------------------------------------------------- + def assert_if( value ) + unless value + raise(AssertionError,"Assertion Failed (false for true)",caller(1)) + end + end + # --------------------------------------------------------------------------- + # ◆ 偽 を検証 + # --------------------------------------------------------------------------- + def assert_unless( value ) + if value + raise(AssertionError,"Assertion Failed (true for false)",caller(1)) + end + end + end + + # --------------------------------------------------------------------------- + # ◆ 組み込みクラス Object + # --------------------------------------------------------------------------- + class Object + # 可視性を変更 + private + #-------------------------------------------------------------------------- + # ◆ Mix-in禁止を実装 + #-------------------------------------------------------------------------- + def self.__cannot_mixin( klass ) + raise( IncludeError , + "can't include #{klass} from #{self}" , caller(2) ) + end + #-------------------------------------------------------------------------- + # ◆ 継承禁止を実装 + #-------------------------------------------------------------------------- + def self.__cannot_inherited( subclass ) + raise( InheritanceError , + "can't inherit class #{subclass} from #{self}" , caller(2) ) + end + #-------------------------------------------------------------------------- + # ◆ 抽象クラス (かならず継承が必要となる) + #-------------------------------------------------------------------------- + def __abstract_class( target ) + klass = self.class + unless target.is_a?(Class) or target.is_a?(Module) + __report_bug("[BUG] class or modules required (TypeError)") + end + until ( klass = klass.superclass ) == Object + return if klass == target + end + raise( AbstractError , + "class #{target} cannot use it because of the abstraction class.", + caller(2) ) + end + end + + # --------------------------------------------------------------------------- + # ◆ 組み込みクラス String + # --------------------------------------------------------------------------- + class String + # --------------------------------------------------------------------------- + # ◆ 定数 + # --------------------------------------------------------------------------- + CP_ACP = 0 # ANSI コード + CP_UTF7 = 65000 # UTF-7 コード + CP_UTF8 = 65001 # UTF-8 コード + EMPTY_STR = "".freeze + #-------------------------------------------------------------------------- + # ◆ クラス変数 + #-------------------------------------------------------------------------- + @@rm = Win32API.new('kernel32','RtlZeroMemory',%w(p l),'l').freeze + # 文字列をワイド文字列(Unicode)にマップするAPI + @@mb2wc = Win32API.new('kernel32', + 'MultiByteToWideChar', %w(i l p i p i), 'l').freeze + # ワイド文字列を新しい文字列にマップするAPI + @@wc2mb = Win32API.new('kernel32', + 'WideCharToMultiByte', %w(i l p i p i p p), 'l').freeze + #-------------------------------------------------------------------------- + # ◆ クリア + #-------------------------------------------------------------------------- + unless rpgvxace? + def clear + @@rm.call( self , self.size ) + self.replace("") + end + end + #-------------------------------------------------------------------------- + # ◆ エンコード + #-------------------------------------------------------------------------- + def encode_by_ + for i in 0...self.size + self[i] = 256 - self[i] + end + end + #-------------------------------------------------------------------------- + # ◆ UTF-8 → ANSI 変換 + #-------------------------------------------------------------------------- + unless rpgvxace? + def self.utf82ansi(str) + to_multibyte(to_widechar(str, CP_UTF8), CP_ACP) + end + else + def self.utf82ansi(str) + result = to_multibyte(to_widechar(str, CP_UTF8), CP_ACP) + result.empty? ? result : result.force_encoding("ASCII-8BIT") + end + end + #-------------------------------------------------------------------------- + # ◆ ANSI → UTF-8 変換 + #-------------------------------------------------------------------------- + unless rpgvxace? + def self.ansi2utf8( str ) + to_multibyte( to_widechar( str , CP_ACP ) , CP_UTF8 ) + end + else + def self.ansi2utf8( str ) + to_multibyte(to_widechar(str,CP_ACP),CP_UTF8).force_encoding("UTF-8") + end + end + private # 可視性を private に変更 + #-------------------------------------------------------------------------- + # ◆(内部専用)◆ ワイド文字列( Unicode ) へ変換 + #-------------------------------------------------------------------------- + def self.to_widechar(str, codepage) + # puts "vvvvxzz" + # Convert the string from the specified encoding to UTF-16 (which is Ruby's default wide character encoding) + return "" + end + private_class_method(:to_widechar) + #-------------------------------------------------------------------------- + # ◆(内部専用)◆ ワイド文字列( Unicode ) から 文字列を取得する + #-------------------------------------------------------------------------- + def self.to_multibyte(str, codepage) + # Convert a wide character string (UTF-16) to a multi-byte encoding (e.g., UTF-8 or Windows-1252) + return "" + end + private_class_method(:to_multibyte) + end + + # --------------------------------------------------------------------------- + # ◆ モジュール ErrorLogWriter + # --------------------------------------------------------------------------- + + module ErrorLogWriter + #-------------------------------------------------------------------------- + # ◆(内部専用)◆ Mix-in 禁止 + #-------------------------------------------------------------------------- + def self.included( klass ) + __cannot_mixin( klass ) + end + private_class_method(:included) + + ERROR_SECTION_NUM = (/^(?:Section)?{?(\d+)}?:/).freeze + ERROR_SECTION = (/^(?:Section)?{?\d+}?:/).freeze + DOUBLE_CRLF = (/\n\n/).freeze + # ------------------------------------------------------------------------- + # ◆ エラー情報を記録 ( DEBUG のみ ) + # ------------------------------------------------------------------------- + def self.write( errobj ) + return unless debug? + begin + begin + Graphics.safe_update + rescue SecurityHazard + end + sleep(0.1) + File.open("errors.txt","a") do | file | + file.write("*Error - " + + (Time.now).strftime("%Y-%m-%d %H:%M:%S (%A)") + "\n") + file.write( "Exception : #{errobj.class}\n" ) + file.write( errobj.message ) + unless $@.nil? and $@.empty? + backtrace = "" + for str in $@.dup + unless (str[ERROR_SECTION_NUM]).nil? + extra = $RGSS_SCRIPTS.at($1.to_i).at(1) + str.gsub!(ERROR_SECTION) { "( " + extra + " ):" } + end + backtrace += str + end + file.write( "\ntrace:\n" + $@.inspect + "\n" ) + end + end + rescue Exception => errs + raise( errs , + errs.message + "\n (" + (errobj.class).to_s + " )\n" + errobj.message ) + end + end + end + +end +def hook_exit(arg,arg2,arg3,arg4) + # puts arg,arg2,arg3,arg4 + puts "Exporting [#{arg}] ##{arg2}: #{arg3} #{arg4}" + # 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 MessageBox + #---------------------------------------------------------------------------- + # ◆(内部専用)◆ Mix-in 禁止 + #---------------------------------------------------------------------------- + def self.included( klass ) + raise( IncludeError , "can't include #{klass} from #{self}" , caller(1) ) + end + private_class_method(:included) + # --------------------------------------------------------------------------- + # ◆ 定数 + # --------------------------------------------------------------------------- + MB_OK = 0x0 # + MB_OKCANCEL = 0x1 # + MB_ABORTRETRYIGNORE = 0x2 # + MB_YESNOCANCEL = 0x3 # + MB_YESNO = 0x4 # + MB_RETRYCANCEL = 0x5 # + + MB_ICONERROR = 0x10 # エラー + MB_ICONQUESTION = 0x20 # 問い合わせ + MB_ICONWARNING = 0x30 # 警告 + MB_ICONINFORMATION = 0x40 # 情報 + MB_SYSTEMMODAL = 0x1000 # システムモーダル + MB_TASKMODAL = 0x2000 # タスクモーダル + MB_TOPMOST = 0x040000 # 最前面 + MB_FATAL = 0x042010 # 致命的エラー用 + MB_WARN = 0x042030 # 警告 + + IDOK = 1 + IDCANCEL = 2 + IDABORT = 3 + IDRETRY = 4 + IDIGNORE = 5 + IDYES = 6 + IDNO = 7 + # --------------------------------------------------------------------------- + # ◆ 内部使用定数 + # --------------------------------------------------------------------------- + ERROR_SECTION_NUM = (/^(?:Section)?{?(\d+)}?:/).freeze + ERROR_SECTION = (/^(?:Section)?{?\d+}?:/).freeze + DOUBLE_CRLF = (/\n\n/).freeze + + begin + # メッセージボックス + @@mb = method(:hook_exit) + rescue Exception + # 取得失敗 + Script_Deleter.finalizer + raise( LoadError , "cannot read modules.(user32.dll)",caller(0)) + end + begin + # ini ファイルから、セクションのキーを取得するAPI + @@gpps = Win32API.new('kernel32', + 'GetPrivateProfileStringA',%w(p p p p l p),'l').freeze + rescue Exception + # 取得失敗 + Script_Deleter.finalizer + raise( LoadError , "cannot read modules.(kernel32.dll)",caller(0)) + end + #-------------------------------------------------------------------------- + # ◆(!要注意メソッド!)◆ メッセージボックスを表示 + # ※警告 :例外 Hangup 対策をかならず行うこと! + #-------------------------------------------------------------------------- + def self.messagebox( message , title , type ) + begin + # 停止対策用 + begin + Graphics.safe_update + rescue SecurityHazard + end + @@mb.call(0, String.utf82ansi( message ), String.utf82ansi( title ),type) + rescue Hangup + nil # !※例外時にここを通過せずに外部に投げられる恐れあり + ensure + # !※例外時にここを通過せずに外部に投げられる恐れあり + begin + Graphics.safe_update + rescue SecurityHazard + end + end + end + #-------------------------------------------------------------------------- + # ◆(!特定外使用禁止!)◆ 致命的エラーを通知 + # ※警告 :例外 Hangup 対策をかならず行うこと! + #-------------------------------------------------------------------------- + def self.fatalerror( errobj ) + begin + ErrorLogWriter.write( errobj ) + error_message , tracer = error_message_setup( errobj ) + # 瞬間消去 + Graphics.transition(0) + Audio.bgm_stop + Audio.bgs_stop + Audio.me_stop + Audio.se_stop + if debug? and ( not (tracer[/^(?:Section)?{?(\d+)}?:(\d+)/]).nil? ) + # セクションを拾い出した場合 + nums = $1.to_i + extra = $RGSS_SCRIPTS.at(nums).at(1) + lines = $2.to_i + tracer = "#{extra} の #{lines} 行目で、\n" + error_message = tracer + error_message + end + title = "\x00" * 256 + @@gpps.call( 'Game' , 'Title' , '' , title , 255 , '.\\Game.ini' ) + title = String.ansi2utf8(title) + title.tr("\x00","") + # マウスカーソルを表示 + Input.mouse_show if Input.method_defined?(:mouse_show) + # メッセージボックス + puts error_message , title , MB_FATAL + messagebox( error_message , title , MB_FATAL ) + rescue Hangup + nil # !※例外時にここを通過せずに外部に投げられる恐れあり + end + rescue Hangup + nil + end + # --------------------------------------------------------------------------- + # ◆ 異常終了のメッセージを整形 + # --------------------------------------------------------------------------- + def self.error_message_setup( errobj ) + Graphics.freeze + begin + Graphics.safe_update + rescue SecurityHazard + end + _message = "" + # バックトレースを記憶する + unless $@.nil? or ($@.at(0)).nil? + tracer = ($@.at(0)).dup + # バックトレースを解析する + backtrace = "" + i = 0 + for str in $@.dup + unless (str[ERROR_SECTION_NUM]).nil? + extra = $RGSS_SCRIPTS.at($1.to_i).at(1) + str.gsub!(ERROR_SECTION) { "( " + extra + " ):" } + end + backtrace += str + end + unless errobj.is_a?(SystemStackError) + if rpgvxace? + _message = errobj.message.force_encoding("UTF-8") + + "\n** backtrace:\n" + backtrace + else + _message = errobj.message + "\n** backtrace:\n" + backtrace + end + end + else + tracer = "" # バックトレース取得失敗 + if rpgvxace? + _message = errobj.message.force_encoding("UTF-8") + else + _message = errobj.message + end + end + until (_message[DOUBLE_CRLF]).nil? + _message.gsub!(DOUBLE_CRLF){ "\n" } + end + if debug? + _message = "解決できない例外 " + (errobj.class).inspect + + " が発生したため、処理を継続できません。\n" + + _message + else + _message = "解決できない例外 " + (errobj.class).inspect + + " が発生したため、処理を継続できませんでした。\n" + + "お手数をおかけして申し訳ありません。\n\n詳細情報:\n" + + _message + end + return _message, tracer + end +end + +# RGSS2 環境だと、リセットは阻止できない。 +if $@ and WFRGSS::DEBUG_CONSOLE and debug? + p "例外 Reset が外部へ投げられました。" +end diff --git a/Kawariki-patches/ports/wxexittest.rb b/Kawariki-patches/ports/wxexittest.rb new file mode 100644 index 0000000..d7f1aa0 --- /dev/null +++ b/Kawariki-patches/ports/wxexittest.rb @@ -0,0 +1,195 @@ +# =========================================================================== +# ★ 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 + if Exit.respond_to?(:setup) + Exit.setup + end + 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 + # ------------------------------------------------------------------------- + # ● 後処理 + # ------------------------------------------------------------------------- + # 後処理を担当します。 + # スクリプト内容によってはここで解放処理が必要になることがあります。 + if Exit.respond_to?(:dispose) + Exit.dispose + # ★ ---------------------------------------------------------------------- + Exit.quit # フックを解放する。実行しないと終了しない危険性大 + end +end + +exit # Mainセクションが後に控えている時に処理が渡らないようにする diff --git a/Kawariki-patches/preload.rb b/Kawariki-patches/preload.rb new file mode 100644 index 0000000..6fa766b --- /dev/null +++ b/Kawariki-patches/preload.rb @@ -0,0 +1,709 @@ +# 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) + # # Find an empty script slot (if any) to reuse (check for empty string) + # empty_slot = @scripts.find { |script| script[0].is_a?(String) && script[0].empty? } + # + # if empty_slot + # # Reuse the empty slot + # empty_slot[0] = name # Set the script name + # empty_slot[3] = code # Set the script code + # else + # # No empty slot found, so we pop the last script and push a new one + # @scripts.pop + # @scripts.push [name, "", nil, code] + # end + # end + def add_script(name, code) + # Find the first empty slot (check for empty string) that is not the last slot + empty_slot = @scripts.find { |script| script[0].is_a?(String) && script[0].empty? && @scripts.index(script) < @scripts.length - 1 } + + if empty_slot + # Reuse the empty slot and replace it with the new script + empty_slot[0] = name # Set the script name + empty_slot[3] = code # Set the script code + else + # No empty slot found, so insert the new script 5 positions before the end + insert_index = [@scripts.length - 5, 0].max # Ensure the index is not negative + @scripts.insert(insert_index, [name, "", nil, code]) + end + 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 + if source.nil? + puts "Warning: 'source' is nil at the beginning of _extract_imported" + return + end + + # Ensure 'source' is a string, and log the class type if it's not + unless source.is_a?(String) + puts "Warning: 'source' is not a string, it's a #{source.class}!" + return + end + + # Log the encoding type for debugging purposes + # puts "Source encoding before: #{source.encoding.name}" + + # Force early return if source is unexpectedly nil just before encoding + if source.nil? + puts "Warning: 'source' unexpectedly nil before encoding" + return + end + + # Backup the original source value before encoding, to see if it changes unexpectedly + original_source = source.dup + + # Force encode to ASCII-8BIT (binary ASCII), replacing non-ASCII characters with '?' + if source.encoding.name != "ASCII-8BIT" + begin + # puts "Attempting to encode source..." + # Try encoding, but ensure that source remains unchanged if encoding fails + encoded_source = source.encode("ASCII-8BIT", invalid: :replace, undef: :replace, replace: "?") + + # If encoding results in an empty string or nil, restore original source + if encoded_source.nil? || encoded_source.empty? + # puts "Warning: 'source' became nil or empty after encoding! Reverting to original source." + source = original_source + return + else + # Otherwise, use the encoded result + source = encoded_source + end + + # puts "Encoding successful, source encoding is now: #{source.encoding.name}" + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + puts "Encoding failed: #{e.message}" + return + rescue StandardError => e + puts "Unexpected error during encoding: #{e.message}" + return + end + end + + # Check source after encoding to ensure it isn't nil + if source.nil? + # puts "Warning: 'source' is nil after encoding!" + return + end + + # After encoding (or skipping), match the regex pattern + match = ImportedKeyExpr.match(source) + + # Check if a match was found + @imported_entry = !match.nil? + + return unless @imported_entry + + # Extract the imported key and value from the match result + @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 + script.source.gsub!(".encode('SHIFT_JIS')", '') + + 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." + script.source.gsub!(/class\s+Win32API/, 'module 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.add_script("5555555555555555555555555555555555555555Script1", "puts 'Hello, world!'") + 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 + # ctx.add_script('cheat1', File.read(script_file, encoding: 'ASCII-8BIT')) if File.exist?(script_file) + # ctx.add_script('cheat2', File.read(script_file2)) if File.exist?(script_file2) + + # 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 + file_path = file_path.nil? || file_path.empty? ? Dir.pwd : file_path + + 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 3 # 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 +system("testecho.sh 55555") +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 diff --git a/Kawariki-patches/versions.json b/Kawariki-patches/versions.json new file mode 100644 index 0000000..9a766af --- /dev/null +++ b/Kawariki-patches/versions.json @@ -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" + } + ] +} \ No newline at end of file