=begin Input Ultimate 2.3 by Zeus81 Free for non-commercial use Pour simplement régler les touches sans vouloir en savoir plus, aller ligne 1471. REFERENCES Input : Constantes : REPEAT : Permet de régler la façon dont la méthode repeat? fonctionne, ligne 1417. PLAYERS_MAX : Le nombre de joueurs gérés par le script, à configurer ligne 1424. KEYS_MAX : Définit une limite pour optimiser la mémoire, ligne 1425. Players : La liste des contrôles joueurs. Gamepads : La liste des manettes détectées. Méthodes : Input.update : A appeler une fois par frame pour tout mettre à jour. Input.refresh : Détecte les manettes connectées au PC. Input[i] : Retourne le contrôle du joueur i (commence à 0). Le premier Player est fusionné avec le module Input Faire Input.trigger?(k) équivaut à faire Input[0].trigger?(k) Device : Base commune à Player, Gamepad, Keyboard et Mouse Propriétés : enabled : Permet d'activer ou désactiver un contrôle. Par exemple si on fait un jeu multijoueur mais qu'on joue en solo on peut désactiver les autres joueurs pour optimiser les perfs. Par défaut tous les contrôles sont activés sauf la souris et les manettes non utilisées. ntrigger_max : Option de la fonction ntrigger? décrite plus en dessous. C'est ce qui est utilisé pour les doubles clics de la souris. Par défaut cette option est désactivée (égale à 0) sauf pour la souris où elle est égale à 3 (pour les triples clic). Cette limite sert à faire boucler les clics, par exemple là si je fais 5 clics ça sera considéré comme un triple clic suivi d'un double clic. ntrigger_time : Temps en nombre de frames pouvant s'écouler entre deux clics pour être considérés comme une série de clics. Si on met 0 ce temps sera le même que dans Windows. (Défaut) Méthodes : press?(*a) : Retourne true si la touche est pressée sinon false. La fonction peut être utilisée de plusieurs façons : Input.press?(Input::C) Input.press?(:C) Input.press?('C') Input.press?(13) Input::C.press? Tout ceci revient au même, cela dit en terme de performance Input::C.press? est deux fois plus rapide que les autres. On peut aussi mettre plusieurs touches à la fois : Input.press?(:A, :B, :C) = :A or :B or :C En regroupant des touches dans un tableaux on inverse or/and : Input.press?([:A, :B, :C]) = :A and :B and :C On peut mélanger les deux : Input.press?(:A, [:B, :C]) = :A or (:B and :C) Et mettre plusieurs niveaux de tableaux : Input.press?([:A, [:B, :C]]) = :A and (:B or :C) En plus des tableaux on peut aussi utiliser des Range : Input.press?(:A..:C) = Input.press?([:A, :B, :C]) Input.press?(*:A..:C) = Input.press?(:A, :B, :C) Oui je sais, tout ça ne servira absolument à rien. trigger?(*a) : Retourne true si la touche vient d'être pressée. Pour les arguments fonctionne comme press? release?(*a) : Retourne true si la touche vient d'être relâchée. Pour les arguments fonctionne comme press? repeat?(*a) : Retourne true ou false selon le schéma de REPEAT Pour les arguments fonctionne comme press? ntrigger?(n,*a) : Retourne true si la touche vient d'être pressée comme trigger? mais en comptant le nombre de fois. Mouse.ntrigger?(2, :Left) retournera true uniquement si on fait un double clic, si ensuite on fait un troisième clic seul Mouse.ntrigger?(3, :Left) retournera true. n doit être inférieur ou égal à ntrigger_max Pour les arguments fonctionne comme press? count(k) : Retourne le temps en nombre de frames depuis quand la touche k est pressée, 0 si elle n'est pas pressée. Comme pour le reste k peut être égal à Input::C, :C, 'C', 13. Et on peut aussi écrire Input::C.count key(i) : Retourne la touche Key ayant pour id i. Ici aussi i peut être un symbole, string, nombre (:C, 'C', 13). capture_key(*exclude) : Si jamais vous faites un menu pour configurer les touches utilisez cette fonction pour attendre que le joueur appuie comme ceci : until key = Keyboard.capture_key || Input.gamepad.capture_key Graphics.update Input.update end key sera un Keyboard_Key ou Gamepad_Key, vous pouvez bien entendu les séparer si vous ne voulez que l'un ou l'autre. Protip, avant d'appeler cette méthode utilisez un release? plutôt qu'un trigger? pour éviter que la touche soit capturée. De même il vaut mieux ensuite attendre que la touche capturée soit relâchée avant de continuer, ce qui donne : if Input::C.release? until key = Keyboard.capture_key || Input.gamepad.capture_key Graphics.update Input.update end while key and key.push? Graphics.update Input.update end end Vous pouvez aussi mettre en arguments des touches que vous désirez ignorer. Player < Device : Pour les jeux multijoueur on y accède en utilisant Input[i] Pour simplifier les jeux solo le premier Player est fusionné avec Input : Input.trigger?(*a) = Input[0].trigger?(*a) Constantes : A, B, C, etc... : Les touches du jeu virtuelles (à ne pas confondre avec les touches du clavier), à configurer ligne 1433. Ce ne sont pas des nombres mais des objets de type Player_Key ce qui permet de faire des chose comme : Input::C.press? Pour les jeux multijoueurs on peut aussi faire : Input::C[i].press? où i est l'id du joueur. Propriétés : id : Id du joueur. (lecture seule) gamepad_id : Id du gamepad, par défaut égal à l'id du joueur. Pour que le joueur n'ai pas de gamepad, mettre la valeur -1. gamepad : Contrôleur Gamepad associé au joueur. (lecture seule) Méthodes : setup(h) : Configure la relation entre les touches virtuelles du jeu et les touches réelles des clavier/manettes. Nécessite un format particulier, voir ligne 1471. dir4() : Retourne la direction pressée, format 4 directions. dir8() : Retourne la direction pressée, format 8 directions. dir360() : Retourne la direction pressée sous forme d'un angle et une pression (pourcentage entre 0 et 1). angle, power = Input.dir360 A la base créé pour les joysticks analogiques comme la fonction analog? celle-ci retourne des valeurs aux extrémités si utilisé sans manette analogique. dirXY() : Retourne la direction pressée sous forme de deux axes entre -1 et 1. x, y = Input.dirXY Si le stick est orienté vers la gauche x = -1, vers la droite x = 1. Si le stick est orienté vers le bas y = -1, vers le haut y = 1. Par contre attention, la portée d'un joystick n'est pas carrée (ni ronde d'ailleurs, c'est plus un carré aux bords arrondis) de ce fait si on oriente le stick vers bas gauche par exemple on aura pas x, y = -1, -1 mais plus x, y = -0.8, -0.8 Gamepad < Device : Il y a trois types de gamepad, Multimedia_Gamepad pour les manettes standard, XBox360_Gamepad spécialement pour les manettes Xbox et No_Gamepad quand on a aucune manette, toutes ont les mêmes fonctions. La classe No_Gamepad est là juste pour qu'on ait pas à se soucier de savoir si un pad est branché ou pas avant d'utiliser ses fonctions. Par exemple si on veut utiliser la fonction vibrate! on l'utilise, si une manette xbox est branchée ça vibrera sinon ça fera rien, pas d'erreur. Constantes : AXIS_PUSH : Les joysticks analogiques ont une valeur de pression entre 0 et 32768 par direction. Cette variable contient la valeur au delà de laquelle ces touches seront considérés comme pressés si on utilise la fonction press? par exemple. Par défaut 16384 (soit 50%), à configurer ligne 679. AXIS_DEADZONE : La position de repos d'un joystick analogique (au milieu) n'est pas très stable, par conséquent si on utilise la fonction analog? on obtient jamais 0, une zone morte est établie au centre du joystick pour pallier à ça. Par défaut 6666 (soit 10%), à configurer ligne 679. TRIGGER_PUSH : Les gâchettes LT et RT des manettes XBox360 ont une valeur de pression entre 0 et 255. Cette variable contient la valeur au delà de laquelle ces touches seront considérés comme pressés si on utilise la fonction press? par exemple. Par défaut 0, à configurer ligne 679. Button1, Button2, etc.. : Les touches standard des manettes, liste ligne 681. On les utilise uniquement lors du setup des Player. Gamepad::Button1 LB, RB, etc... : Les touches des manettes xbox, liste ligne 689. On les utilise uniquement lors du setup des Player. XBox360_Gamepad::A Ça revient au même que d'utiliser les touches standard c'est juste l'écriture qui change, utilisez celle que vous préférez. Propriétés : unplugged : Retourne true si la manette est débranchée. (lecture seule) Ça peut être utilisé pour détecter une perte de manette soudaine. Si on a pas de manette depuis le départ ce n'est pas considéré comme une manette débranchée mais comme une manette de type No_Gamepad avec unplugged = false vibration : Permet d'activer ou pas les vibrations. Méthodes : analog?(*a) : Retourne un pourcentage entre 0 et 1 si la touche pressée est analogique (sticks des manettes et gâchettes LT RT pour les pads xbox) sinon retourne 0 ou 1. Cette fonction est là pour les manettes cela dit on écrira jamais : Input.gamepad.analog?(XBox360_Gamepad::AxisLY_0) Mais plutôt : Input.analog?(Input::DOWN) Les touches de la manette étant liées au setup du Player. Pour les arguments fonctionne comme press?. vibrate!(id, speed, fade_in, duration, fade_out) : Fait vibrer la manette, ne fait rien si les vibrations sont désactivées ou que ce n'est pas une manette XBox360. id = 0 ou 1, pour les moteurs gauche ou droite. Ou sinon 2 pour les deux en même temps. speed = pourcentage entre 0 et 1 fade_in = durée de la transition entre le niveau de vibration en cours et celui désiré. duration = durée de la vibration. fade_out = durée de la transition vers 0 à la fin du temps. Exemple : Input.gamepad.vibrate!(2, 0.5, 50, 200, 50) Les deux moteurs vibreront à 50% sur en tout 300 frames. Keyboard < Device : Attention normalement vous ne devriez jamais utiliser le clavier directement. Pour créer des jeux il est préférable d'utiliser des touches virtuelles qui peuvent correspondre à plusieurs touches du clavier + des manettes en même temps et éventuellement être configurable par le joueur, donc utilisation directe du clavier à éviter. Pour ce qui est de la saisie de texte la classe Text_Entry_Box est là. Constantes : Escape, Enter, etc... : Les touches du clavier, voir la liste ligne 862. Ce ne sont pas des nombres mais des objets de type Keyboard_Key ce qui permet de faire des chose comme : Keyboard::Enter.push? Méthodes : setup(*a) : Si toutefois vous étiez amené pour une raison ou une autre à devoir malgré tout utiliser directement le clavier, il faudra d'abord lui dire quelles touches mettre à jour. Par défaut aucune n'est mise à jour parce que ça prendrait du temps pour rien. Comme pour le reste plusieurs écritures sont supportées : Keyboard.setup(Keyboard::Enter, :Escape, 'A'..'Z', 1) Pour désactiver la mise à jour des touches : Keyboard.setup() A chaque setup toutes les touches envoyées remplacent les anciennes, elles ne s'ajoutent pas. Cependant vous n'avez besoin de faire ça que si vous désirez utiliser les fonctions press?, trigger?, release?, repeat?, ntrigger? et count, les fonctions push?, toggle?, push!, release! et toggle! si dessous peuvent être utilisées sur n'importe quelle touche en permanence. push?(*a) : Fonctionne exactement comme la fonction press? mais en regardant directement l'état du matériel donc un peu plus lentement. A n'utiliser que dans le cas cité précédemment, si vous voulez l'état press d'une touche du clavier sans vouloir passer par le setup, sinon il est préférable d'utiliser press? Vous pouvez aussi l'utiliser sur les constantes Key du clavier : Keyboard::Escape.push? toggle?(*a) : Retourne true si la touche est verrouillée sinon false. Le vérouillage est ce qui est utilisé pour les touches Verr. Num, Verr. Maj et Verr. Défil pour savoir si elles sont sur On ou Off, mais vous pouvez l'utiliser sur n'importe quelle touche en fait. Comme push? cette fonction regarde directement l'état du matériel et peut être utilisée sans setup. Vous pouvez aussi l'utiliser sur les constantes Key du clavier : Keyboard::CapsLock.toggle? Pour les arguments fonctionne comme press? push!(*a) : Appuie sur les touches du clavier à la place du joueur. Les signaux sont envoyés directement à Windows donc attention. Vous pouvez par exemple forcer le passage en plein écran : Keyboard.push!(:Alt, :Enter) Vous pouvez aussi l'utiliser sur les constantes Key du clavier : Keyboard::Space.push! Pour les arguments fonctionne comme press? release!(*a) : Relâche les touches du clavier. Après avoir appeler push! il faut appeler release! pour que le système comprenne qu'elle n'est pas appuyée en permanence. Ce n'est pas automatique ! Donc pour finaliser le passage en mode plein écran il faut faire : Keyboard.push!(:Alt, :Enter) Keyboard.release!(:Alt, :Enter) Vous pouvez aussi l'utiliser sur les constantes Key du clavier : Keyboard::Space.release! Pour les arguments fonctionne comme press? toggle!(*a) : Change l'état de verrouillage d'une touche. Vous pouvez aussi l'utiliser sur les constantes Key du clavier : Keyboard::Space.release! Pour les arguments fonctionne comme press? key_name(k) : Retourne le nom de la touche dans la langue du système. Comme pour press?, k peut être égal à Keyboard::W, :W, 'W', 87. Par exemple : Keyboard.key_name(:Escape) # => "ECHAP" Cependant les noms de toutes les touches ne sont pas forcément bienvenus donc il est peut être préférable de s'organiser soi-même une liste de noms manuellement. Vous pouvez aussi l'utiliser sur les constantes Key du clavier : Keyboard::Escape.name Mouse < Device : Par défaut la souris est désactivée, pour l'activer faites : Mouse.enabled = true Constantes : Left, Middle, Right, X1, X2, WheelUp, WheelDown : Les touches de la souris, j'ai tout mis inutile d'aller voir la liste ligne 990. Ce ne sont pas des nombres mais des objets de type Mouse_Key ce qui permet de faire des chose comme : Mouse::Left.click? Left, Middle, Right sont les clics gauche, milieu, droit. X1 et X2 sont les boutons sur les côtés de la souris (si y'en a). WheelUp et WheelDown pour la roulette ne répondent malheureusement pas parfaitement, à éviter dans un jeu du coup, d'ailleurs pour que ça marche un tant soit peu il faut mettre le Input.update avant le Graphics.update Propriétés : click_max : alias de ntrigger_max Contrairement aux autres contrôles le click_max/ntrigger_max de la souris est par défaut égal à 3. click_time : alias de ntrigger_time cursor : Le curseur de la souris, c'est un Sprite classique donc vous pouvez utiliser toutes les fonctions des sprites. Pour modifier le curseur par défaut voir ligne 1024. x : Raccourci de cursor.x pour la lecture. Vous pouvez aussi modifier manuellement la position de la souris. Mouse.x = 123 Attention Mouse.cursor.x = 123 ne fonctionne pas. y : Raccourci de cursor.y pour la lecture. Vous pouvez aussi modifier manuellement la position de la souris. Mouse.y = 456 Attention Mouse.cursor.y = 456 ne fonctionne pas. drag : Le rectangle de sélection utilise aussi un Sprite mais a un fonctionnement particulier. Il apparait automatiquement quand on maintient un clic gauche et déplace la souris, cependant par défaut il est désactivé. Si vous désirez changer l'apparence du rectangle de sélection sachez que c'est un Bitmap de 1x1 pixels, par défaut il est bleu transparent voir ligne 1032, mais on peut le changer à tout moment : Mouse.drag.bitmap.set_pixel(0, 0, Color.new(255, 0, 0, 128)) drag_enabled = Permet d'activer/désactiver la sélection, par défaut désactivée. Pour l'activer : Mouse.drag_enabled = true drag_x : Raccourci de drag.x (Lecture seule) drag_y : Raccourci de drag.y (Lecture seule) drag_width : Largeur de la sélection, équivalent de drag.zoom_x.to_i (Lecture seule) drag_height : Hauteur de la sélection, équivalent de drag.zoom_y.to_i (Lecture seule) drag_rect : Retourne Rect.new(drag_x, drag_y, drag_width, drag_height) (Lecture seule) drag_auto_clear : Si activé la sélection s'effacera automatiquement dès lors qu'on relâche le clic gauche. drag_start_size : Nombre de pixels dont on doit déplacer la souris pour faire apparaître le rectangle de sélection, par défaut 10. Méthodes : icon(s=nil, ox=0, oy=0) : Permet de changer l'apparence du curseur, l'image doit se trouver dans le dossier picture, l'extension est facultative. Mouse.icon("croix") On peut éventuellement préciser un décalage vers le centre du curseur si nécessaire, en comptant à partir du coin haut gauche de l'image. Mouse.icon("croix", 4, 4) Si on veut remettre le curseur par défaut : Mouse.icon() clip(*a) : Définit la zone dans laquelle la souris est libre de se mouvoir. Il y a quatre façons d'utiliser cette fonction : Mouse.clip(i) : Si i = 0, aucune limitation, la souris peut voguer librement sur tout l'écran. (Défaut) Si i = 1, la souris est bloquée dans la fenêtre du jeu, càd surface de jeu + barre de titre. Si i = 2, la souris est bloquée dans la surface de jeu, on ne peut plus aller sur la barre de titre. Déconseillé vu qu'on ne peut plus réduire/fermer le jeu ce qui peut vite devenir pénible. Mouse.clip() : Idem que Mouse.clip(0). Mouse.clip(x, y, w, h) : Définit une zone manuellement, le point 0, 0 correspond au coin haut gauche de la surface de jeu. Mouse.clip(rect) : Idem mais avec un objet de type Rect. on?(*a) : Retourne true si la souris est dans la zone demandée. Il y a cinq façons d'utiliser cette fonction : Mouse.on?() : vérifie si le curseur est dans la surface de jeu. Mouse.on?(x, y) : vérifie si le curseur est sur le point x y. Mouse.on?(x, y, r) : vérifie si le curseur est dans le cercle de centre x y et de rayon r. Mouse.on?(x, y, w, h) : vérifie si le curseur est dans le rectangle aux coordonnées spécifiées. Mouse.on?(rect) : Idem mais avec un objet de type Rect. drag_on?(*a) : Retourne true si la sélection est en contact avec la zone donnée. Il y a quatre façons d'utiliser cette fonction : Mouse.drag_on?(x, y) : vérifie si le point x y est compris dans la sélection. Mouse.drag_on?(x, y, r) : vérifie si le cercle de centre x y et de rayon r entre en contact avec la sélection. Mouse.drag_on?(x, y, w, h) : vérifie si le rectangle entre en contact avec la sélection. Mouse.drag_on?(rect) : Idem mais avec un objet de type Rect. drag_clear() : Efface la sélection. dragging?() : Retourne true si une sélection est en cours. swap_button(b) : Si b = true, inverse les clics gauches et droits, si b = false, les remet à la normale. click?(k=Left) : Simple clic, équivalent de ntrigger?(1, k). L'argument est facultatif et est le clic gauche par défaut. La différence entre click? et trigger? est que si on fait un double clic par exemple lors du deuxième clic trigger? renverra true alors que click? false (et dclick? true). Vous pouvez aussi l'utiliser sur les constantes Key de la souris : Mouse::Left.click? dclick?(k=Left) : Double clic, équivalent de ntrigger?(2, k). Vous pouvez aussi l'utiliser sur les constantes Key de la souris : Mouse::Left.dclick? tclick?(k=Left) : Triple clic, équivalent de ntrigger?(3, k). Vous pouvez aussi l'utiliser sur les constantes Key de la souris : Mouse::Left.tclick? Key : Il y en a quatre types : Player_Key, Gamepad_Key, Keyboard_Key et Mouse_Key Certaines possèdent des fonctions en plus. Méthodes : to_s() : Retourne le nom de la constante. Input::DOWN.to_s # => "DOWN" to_i() : Retourne l'id de la touche. Input::DOWN.to_i # => 2 push?(), toggle?(), press?(), trigger?(), release?(), repeat?(), analog?(), ntrigger?(n), count() : Toutes les fonctions décrites précédemment sont utilisables directement sur les touches pour plus d'efficacité : Input::DOWN.press? Cependant inutile d'appeler ces fonctions sur les touches Gamepad_Key, ça ne marchera pas. name(), push!(), release!(), toggle!() : Fonctions supplémentaires des Keyboard_Key click?(), dclick?(), tclick?() : Fonctions supplémentaires des Mouse_Key [i] : Fonction supplémentaire des Player_Key pour le jeu multijoueur : Input::DOWN[0].trigger? # joueur 1 Input::DOWN[1].trigger? # joueur 2 Text_Entry_Box < Sprite Cette classe permet de saisir du texte au clavier, la souris est aussi supportée pour la sélection de texte. Text_Entry_Box est dérivée de Sprite et son affichage est automatisé. Vous pouvez utiliser toutes les fonctions de Sprite cependant les fonctions zoom, angle, mirror, src_rect ne seront pas compatibles avec la souris. Si la souris est activée et survole une boite de texte, l’icône changera automatiquement, vous pouvez modifier son apparence ligne 1180. Propriétés : enabled : Permet d'activer ou désactiver une boite de texte. Une boite désactivée ne peut plus avoir le focus. focus : L'état de focus actuel, une seule boite peut l'avoir à la fois. text : Permet de récupérer le texte entré. back_color : La couleur du fond, valeur par défaut ligne 1195. Elle peut être modifiée à tout moment mais il faut ensuite appeler refresh() pour mettre à jour le bitmap. text_entry_box.back_color = Color.new(255, 0, 0) select_color : Couleur de la sélection, idem que back_color. allow_c : Liste des caractères autorisés sous forme de string. text_entry_box.allow_c = "abcABC123" Si la liste est vide tous les caractères sont autorisés. (Défaut) select : Permet d'activer ou désactiver les sélections que ce soit avec la souris ou en maintenant la touche Maj. Activé par défaut, pour le désactiver : text_entry_box.select = false mouse : Permet d'activer ou désactiver les fonctionnalités de la souris. Activé par défaut, pour le désactiver : text_entry_box.mouse = false Il faut aussi que la souris elle même soit activée sinon ça sert à rien. case : Casse de caractère : text_entry_box.case = 0 # normal, sensible à la casse (Défaut) text_entry_box.case = 1 # downcase, les majuscules deviennent minuscules. text_entry_box.case = 2 # upcase, les minuscules deviennent majuscules. size : Le nombre de caractères actuel. (lecture seule) size_max : Le nombre de caractères maximum autorisé. Si 0, pas de limite. width : Raccourci pour text_entry_box.bitmap.width (lecture seule) width_max : La taille maximum autorisée en pixels. Si 0, pas de limite. height : Raccourci pour text_entry_box.bitmap.height (lecture seule) font : Raccourci pour text_entry_box.bitmap.font Méthodes : Text_Entry_Box.new(width, height, viewport=nil) : Crée une nouvelle instance de Text_Entry_Box On est obligé de préciser width et height qui sont utilisés pour créer le bitmap, viewport est facultatif. text_entry_box = Text_Entry_Box.new(100, 20) dispose : Supprime le sprite et le bitmap. update : A appeler une fois par frame pour mettre à jour la saisie de texte. refresh : A appeler après avoir modifié les propriétés ou le font du bitmap pour forcer la mise à jour. hover? : Retourne true si la souris survole la boite texte. focus! : Donne le focus à la boite texte, une seule boite peut avoir le focus à la fois, il est automatiquement retiré des autres. Cette fonction est automatiquement appelée quand on clique sur la boite texte, si la souris est activée bien sûr. =end class Object remove_const(:Input) def singleton_attach_methods(o) class << self; self end.attach_methods(o) end def self.attach_methods(o) @attached_methods ||= [] for m in o.public_methods-(instance_methods-@attached_methods) define_method(m, &o.method(m)) @attached_methods << m unless @attached_methods.include?(m) end end end class String; alias getbyte [] end if RUBY_VERSION == '1.8.1' module Input class Key include Comparable attr_reader :o, :i, :s, :hash, :to_sym alias to_i i alias to_s s def initialize(i,o) @o, self.i, self.s = o, i, "#{self.class.name.split('::')[-1]}_#{i}" end def o=(o) @o, @hash = o, @i.hash^o.hash end def i=(i) @i, @hash = i, i.hash^@o.hash end def s=(s) @s, @to_sym = s, s.to_sym end def <=>(o) @i <=> o.to_i end def succ() self.class.new(@i+1,@o) end def count() @o.get_count(@i) end def push?() @o.get_push(@i) end def toggle?() @o.get_toggle(@i) end def press?() @o.get_press(@i) end def trigger?() @o.get_trigger(@i) end def release?() @o.get_release(@i) end def repeat?() @o.get_repeat(@i) end def ntrigger?(n) @o.get_ntrigger(n,@i) end def analog?() @o.get_analog(@i) end end class Gamepad_Key < Key def initialize(i,o=Gamepad) super end end class Keyboard_Key < Key def initialize(i,o=Keyboard) super end def name() @o.get_key_name(@i) end def push!() @o.push!(@i) end def release!() @o.release!(@i) end def toggle!() @o.toggle!(@i) end end class Mouse_Key < Key def initialize(i,o=Mouse) super end def click?() ntrigger?(1) end def dclick?() ntrigger?(2) end def tclick?() ntrigger?(3) end end class Player_Key < Key def [](i) @players_keys[i] end def initialize(i,o=Players[0]) super @players_keys = Players.map {|p| k = dup; k.o = p; k} end end class Device GetDoubleClickTime = Win32API.new('user32', 'GetDoubleClickTime', '', 'i') attr_accessor :enabled, :ntrigger_max, :ntrigger_time def initialize(max) @enabled, @count, @release, @keys = true, Array.new(max,0), [], [] @ntrigger_count, @ntrigger_last, @ntrigger_max = @count.dup, @count.dup, 0 self.ntrigger_time = 0 end def update return unless @enabled update_keys update_ntrigger if @ntrigger_max != 0 end def update_keys @release.clear for i in @keys if get_push(i) ; @count[i] += 1 elsif @count[i] != 0; @count[i] = 0; @release << i end end end def update_ntrigger f = Graphics.frame_count for i in @keys if @count[i] == 1 @ntrigger_count[i] %= @ntrigger_max @ntrigger_count[i] += 1 @ntrigger_last[i] = f + @ntrigger_time elsif @ntrigger_last[i] == f @ntrigger_count[i] = 0 end end end def capture_key(*exclude) exclude = keyarrayize(*exclude) unless exclude.empty? (@count.size-1).downto(0) {|i| return key(i) if !exclude.include?(i) and get_push(i)} nil end def get_count(i) @count[i] end def get_push(i) false end def get_toggle(i) false end def get_press(i) @count[i] != 0 end def get_trigger(i) @count[i] == 1 end def get_release(i) @release.include?(i) end def get_repeat(i) (j=@count[i])>0 and REPEAT.any? {|w,f| break(f>0 && j%f==0) if j>=w} end def get_ntrigger(n,i) get_trigger(i) and @ntrigger_count[i] == n end def get_analog(i) get_push(i) ? 1.0 : 0.0 end def count(k) get_count(k2i(k)) end def push?(*a) a.any?{|i| enum?(i) ? i.all?{|j| push?(*j)} : get_push(k2i(i))} end def toggle?(*a) a.any?{|i| enum?(i) ? i.all?{|j| toggle?(*j)} : get_toggle(k2i(i))} end def press?(*a) a.any?{|i| enum?(i) ? i.all?{|j| press?(*j)} : get_press(k2i(i))} end def trigger?(*a) a.any?{|i| enum?(i) ? i.all?{|j| trigger?(*j)} : get_trigger(k2i(i))} end def release?(*a) a.any?{|i| enum?(i) ? i.all?{|j| release?(*j)} : get_release(k2i(i))} end def repeat?(*a) a.any?{|i| enum?(i) ? i.all?{|j| repeat?(*j)} : get_repeat(k2i(i))} end def ntrigger?(n,*a) a.any?{|i| enum?(i) ? i.all?{|j| ntrigger?(n,*j)} : get_ntrigger(n,k2i(i))} end def analog?(*a) a.each do |i| d = if enum?(i) sum = size = 0 i.each {|j| sum, size = sum+analog?(*j), size+1} sum == 0 ? 0 : sum / size else get_analog(k2i(i)) end return d if d != 0 end 0.0 end def ntrigger_time=(i) @ntrigger_time = (i==0 ? GetDoubleClickTime.call * Graphics.frame_rate / 1000 : i) end def key(o) self.class.key(o) end def k2i(o) self.class.k2i(o) end private def enum?(o) o.is_a?(Array) or o.is_a?(Range) end def keyarrayize(*a) a.flatten! a.map! {|o| o.is_a?(Range) ? o.to_a : o}.flatten! a.compact! a.map! {|k| k2i(k)}.uniq! a end def self.key(o) o.is_a?(Key) || o.is_a?(Integer) ? const_get(:Keys)[o.to_i] : const_get(o) end def self.k2i(o) o.is_a?(Key) ? o.i : o.is_a?(Integer) ? o : const_get(o).i end end class Player < Device attr_reader :id, :gamepad, :gamepad_id def initialize(id) super(KEYS_MAX) @id, @gamepad_id, @gamepad, @map = id, id, No_Gamepad.new, @count.map{[]} end def setup(h) @keys.clear @count.fill(0) @map.fill([]) for i,a in h a=@map[i=k2i(i)] = a[0].map {|j| Gamepad.key(j).dup} + a[1].map {|j| Keyboard.key(j)} @keys << i unless a.empty? end self.gamepad_id += 0 end def gamepad_id=(i) vibration, @gamepad.enabled = @gamepad.vibration, false @gamepad = (@gamepad_id = i) >= 0 && Gamepads[i] || No_Gamepad.new @gamepad.vibration = vibration Players.each {|p| p.gamepad.enabled = true} @map.each {|a| a.each {|k| k.o = @gamepad if k.is_a?(Gamepad_Key)}} end def get_push(i) @map[i].any? {|k| k.push?} end def get_toggle(i) @map[i].any? {|k| k.toggle?} end def get_analog(i) @map[i].each {|k| d=k.analog?; return d if d != 0}; 0.0 end def dirXY return 0.0, 0.0 unless @enabled return RIGHT.analog?-LEFT.analog?, UP.analog?-DOWN.analog? end def dir360 x, y = *dirXY return 0.0, 0.0 if x == 0 and y == 0 return Math.atan2(y,x)*180/Math::PI, (w=Math.hypot(x,y))>1 ? 1.0 : w end def dir8 d = 5 d -= 3 if DOWN.press? d -= 1 if LEFT.press? d += 1 if RIGHT.press? d += 3 if UP.press? d == 5 ? 0 : d end def dir4 case d = dir8 when 1; DOWN.trigger? == (@last_dir==2) ? 2 : 4 when 3; DOWN.trigger? == (@last_dir==2) ? 2 : 6 when 7; UP.trigger? == (@last_dir==8) ? 8 : 4 when 9; UP.trigger? == (@last_dir==8) ? 8 : 6 else @last_dir = d end end end class Gamepad < Device ::Gamepad = self AXIS_PUSH, AXIS_DEADZONE, TRIGGER_PUSH = 16384, 6666, 0 Keys = Array.new(48) {|i| Gamepad_Key.new(i)} Button1 , Button2 , Button3 , Button4 , Button5 , Button6 , Button7 , Button8 , Button9 , Button10, Button11, Button12, Button13, Button14, Button15, Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, Button32, Axis1_0 , Axis1_1 , Axis2_0 , Axis2_1 , Axis3_0 , Axis3_1 , Axis4_0 , Axis4_1 , Axis5_0 , Axis5_1 , Axis6_0 , Axis6_1 , PovUp , PovRight, PovDown , PovLeft = *Keys XKeys = Array.new(48) {|i| Gamepad_Key.new(i)} A, B, X, Y, LB, RB, LT, RT, BACK, START, LS, RS, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, AxisLX_0, AxisLX_1, AxisLY_1, AxisLY_0, AxisRX_0, AxisRX_1, AxisRY_1, AxisRY_0, n, n, n, n, DPadUp, DPadRight, DPadDown, DPadLeft = *XKeys constants.each {|s| k = const_get(s); k.s = s.to_s if k.is_a?(Key)} attr_accessor :vibration attr_reader :unplugged def initialize(id=nil) super(48) @id, @unplugged, @map, @vibration = id, false, @count.map{[]}, true end def get_push(i) return false unless @enabled and !@unplugged j, k = *@map[i] case j when :button ; button(k) when :pov ; k.include?(pov) when :axis_0 ; axis_raw(k) < -AXIS_PUSH when :axis_1 ; axis_raw(k) > AXIS_PUSH-1 when :trigger; trig_raw(k) > TRIGGER_PUSH else false end end def get_analog(i) return 0.0 unless @enabled and !@unplugged j, k = *@map[i] case j when :button, :pov; super when :axis_0 ; (k=axis_pct(k)) < 0 ? -k : 0.0 when :axis_1 ; (k=axis_pct(k)) > 0 ? k : 0.0 when :trigger; trig_pct(k) else 0.0 end end def vibrate!(id, speed, fade_in, duration, fade_out) end private def axis_pct(i) (i=axis_raw(i)).abs <= AXIS_DEADZONE ? 0.0 : (i<0 ? i+AXIS_DEADZONE : i-AXIS_DEADZONE+1) / (32768.0-AXIS_DEADZONE) end def trig_pct(i) trig_raw(i) / 255.0 end def axis_raw(i) 0 end def trig_raw(i) 0 end def pov() 0 end def button(i) false end singleton_attach_methods(@o = new) class No_Gamepad < Gamepad ::No_Gamepad = Input::No_Gamepad = self def get_push(i) false end def get_analog(i) 0.0 end end class Multimedia_Gamepad < Gamepad ::Multimedia_Gamepad = Input::Multimedia_Gamepad = self JoyGetDevCaps = Win32API.new('winmm', 'joyGetDevCaps', 'ipi', 'i') JoyGetPosEx = Win32API.new('winmm', 'joyGetPosEx' , 'ip' , 'i') def initialize(id) super JoyGetDevCaps.call(id, devcaps="\0"*404, 404) @caps = Array.new(7) {|i| i<2 or devcaps.getbyte(96)[i-2]==1} @buffer = [52,255,0,0,0,0,0,0,0,0,0,0,0].pack('L13') @state = @buffer.unpack('L13') for k,v in { Button1 =>[:button, 0], Button2 =>[:button, 1], Button3 =>[:button, 2], Button4 =>[:button, 3], Button5 =>[:button, 4], Button6 =>[:button, 5], Button7 =>[:button, 6], Button8 =>[:button, 7], Button9 =>[:button, 8], Button10=>[:button, 9], Button11=>[:button,10], Button12=>[:button,11], Button13=>[:button,12], Button14=>[:button,13], Button15=>[:button,14], Button16=>[:button,15], Button17=>[:button,16], Button18=>[:button,17], Button19=>[:button,18], Button20=>[:button,19], Button21=>[:button,20], Button22=>[:button,21], Button23=>[:button,22], Button24=>[:button,23], Button25=>[:button,24], Button26=>[:button,25], Button27=>[:button,26], Button28=>[:button,27], Button29=>[:button,28], Button30=>[:button,29], Button31=>[:button,30], Button32=>[:button,31], Axis1_0 =>[:axis_0, 0], Axis1_1 =>[:axis_1, 0], Axis2_0 =>[:axis_0, 1], Axis2_1 =>[:axis_1, 1], Axis3_0 =>[:axis_0, 2], Axis3_1 =>[:axis_1, 2], Axis4_0 =>[:axis_0, 3], Axis4_1 =>[:axis_1, 3], Axis5_0 =>[:axis_0, 4], Axis5_1 =>[:axis_1, 4], Axis6_0 =>[:axis_0, 5], Axis6_1 =>[:axis_1, 5], PovUp =>[:pov,[31500, 0, 4500]], PovRight=>[:pov,[ 4500, 9000,13500]], PovDown =>[:pov,[13500,18000,22500]], PovLeft =>[:pov,[22500,27000,31500]]} @map[k.i] = v end update end def update return unless @enabled and !@unplugged = JoyGetPosEx.call(@id, @buffer) != 0 @state.replace(@buffer.unpack('L13')) super end private def button(i) @state[8][i] == 1 end def pov() @caps[6] ? @state[10] : 0 end def axis_raw(i) @caps[i] ? @state[2+i]-32768 : 0 end end class XBox360_Gamepad < Gamepad ::XBox360_Gamepad = Input::XBox360_Gamepad = self Keys = XKeys XInputGetState = (Win32API.new(DLL='xinput1_3' , 'XInputGetState', 'ip', 'i') rescue Win32API.new(DLL='xinput1_2' , 'XInputGetState', 'ip', 'i') rescue Win32API.new(DLL='xinput1_1' , 'XInputGetState', 'ip', 'i') rescue Win32API.new(DLL='xinput9_1_0', 'XInputGetState', 'ip', 'i') rescue DLL=nil) XInputSetState = Win32API.new(DLL , 'XInputSetState', 'ip', 'i') if DLL def initialize(id) super @buffer = "\0"*16 @state = @buffer.unpack('LSC2s4') @vibration_state = Array.new(2) {[0,0,0,0,0,false]} for k,v in { A =>[:button,12], B =>[:button,13], X =>[:button,14], Y =>[:button,15], LB =>[:button, 8], RB =>[:button, 9], LT =>[:trigger,0], RT =>[:trigger,1], BACK =>[:button, 5], START =>[:button, 4], LS =>[:button, 6], RS =>[:button, 7], AxisLX_0 =>[:axis_0, 0], AxisLX_1=>[:axis_1, 0], AxisLY_1=>[:axis_1, 1], AxisLY_0 =>[:axis_0, 1], AxisRX_0=>[:axis_0, 2], AxisRX_1=>[:axis_1, 2], AxisRY_1 =>[:axis_1, 3], AxisRY_0=>[:axis_0, 3], DPadUp =>[:button, 0], DPadRight=>[:button, 3], DPadDown=>[:button, 1], DPadLeft=>[:button, 2]} @map[k.i] = v end update end def update return unless @enabled and !@unplugged = XInputGetState.call(@id, @buffer) != 0 @state.replace(@buffer.unpack('LSC2s4')) super update_vibration if @vibration end def update_vibration vibrate = false @vibration_state.each do |v| next unless v[5] last_v0 = v[0] if v[2]>0; v[0] = (v[0]*(v[2]-=1)+v[1]) / (v[2]+1.0) elsif v[3]>0; v[0], v[3] = v[1], v[3]-1 elsif v[4]>0; v[0] = v[0]*(v[4]-=1) / (v[4]+1.0) else v[0], v[5] = 0, false end vibrate = true if last_v0 != v[0] end set_vibration if vibrate end def vibration=(b) vibrate!(2,0,0,0,0); super end def vibrate!(id, speed, fade_in, duration, fade_out) case id when 0, 1; @vibration_state[id][1,5] = [speed, fade_in, duration, fade_out, true] when 2 ; 2.times {|i| vibrate!(i, speed, fade_in, duration, fade_out)} end end private def button(i) @state[1][i] == 1 end def axis_raw(i) @state[4+i] end def trig_raw(i) @state[2+i] end def set_vibration return unless @enabled and @vibration and !@unplugged XInputSetState.call(@id, [@vibration_state[0][0]*0xFFFF, @vibration_state[1][0]*0xFFFF].pack('S2')) end end end class Keyboard < Device ::Keyboard = self GetKeyboardState = Win32API.new('user32' , 'GetKeyboardState' , 'p' , 'i') getKeyNameText = Win32API.new('user32' , 'GetKeyNameTextW' , 'ipi' , 'i') mapVirtualKey = Win32API.new('user32' , 'MapVirtualKey' , 'ii' , 'i') SendInput = Win32API.new('user32' , 'SendInput' , 'ipi' , 'i') ToUnicode = Win32API.new('user32' , 'ToUnicode' , 'iippii' , 'i') WideCharToMultiByte = Win32API.new('kernel32', 'WideCharToMultiByte', 'iipipipp', 'i') Keys = Array.new(256) {|i| Keyboard_Key.new(i)} None, MouseL, MouseR, Cancel, MouseM, MouseX1, MouseX2, n, Back, Tab, LineFeed, n, Clear, Enter, n, n, Shift, Control, Alt, Pause, CapsLock, KanaMode, n, JunjaMode, FinalMode, KanjiMode, n, Escape, IMEConvert, IMENonConvert, IMEAccept, IMEModeChange, Space, PageUp, PageDown, End, Home, Left, Up, Right, Down, Select, Print, Execute, PrintScreen, Insert, Delete, Help, D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, n, n, n, n, n, n, n, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, LWin, RWin, Apps, n, Sleep, NumPad0, NumPad1, NumPad2, NumPad3, NumPad4, NumPad5, NumPad6, NumPad7, NumPad8, NumPad9, Multiply, Add, Separator, Subtract, Decimal, Divide, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, n, n, n, n, n, n, n, n, NumLock, Scroll, n, n, n, n, n, n, n, n, n, n, n, n, n, n, LShift, RShift, LControl, RControl, LAlt, RAlt, BrowserBack, BrowserForward, BrowserRefresh, BrowserStop, BrowserSearch, BrowserFavorites, BrowserHome, VolumeMute, VolumeDown, VolumeUp, MediaNextTrack, MediaPreviousTrack, MediaStop, MediaPlayPause, LaunchMail, SelectMedia, LaunchApplication1, LaunchApplication2, n, n, OemSemicolon, OemPlus, OemComma, OemMinus, OemPeriod, OemQuestion, OemTilde, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, OemOpenBrackets, OemPipe, OemCloseBrackets, OemQuotes, Oem8, n, n, OemBackslash, n, n, ProcessKey, n, Packet, n, n, n, n, n, n, n, n, n, n, n, n, n, n, Attn, Crsel, Exsel, EraseEof, Play, Zoom, NoName, Pa1, OemClear, Unknown = *Keys constants.each {|s| k = const_get(s); k.s = s.to_s if k.is_a?(Key)} SCAN_CODE = Array.new(256) {|i| mapVirtualKey.call(i, 0)} (BrowserBack..LaunchApplication2).each {|k| SCAN_CODE[k.i] = 0} for k,code in {Pause =>0x0045, PageUp =>0x0149, PageDown=>0x0151, End =>0x014F, Home =>0x0147, Left =>0x014B, Up =>0x0148, Right =>0x014D, Down =>0x0150, PrintScreen=>0x0137, Insert =>0x0152, Delete=>0x0153, LWin =>0x015B, RWin =>0x015C, Apps =>0x015D, Divide=>0x0135, NumLock=>0x0145, RControl =>0x011D, RAlt =>0x0138} SCAN_CODE[k.i] = code end KEYS_NAME = Array.new(256) do |i| if getKeyNameText.call(SCAN_CODE[i]<<16, utf16="\0"*256, 256) > 0 WideCharToMultiByte.call(65001, 0, utf16, -1, utf8="\0"*256, 256, 0, 0) utf8.delete("\0") else Keys[i].to_s end end TEXT_KEYS = [Space.i] + (D0.i..D9.i).to_a + (A.i..Z.i).to_a + (NumPad0.i..Divide.i).to_a + (146..150).to_a + (OemSemicolon.i..OemTilde.i).to_a + (OemOpenBrackets.i..245).to_a TEXT_ENTRY_KEYS = TEXT_KEYS + [Back.i,Left.i,Up.i,Right.i,Down.i,Delete.i] DEAD_KEYS = { '`' => {'a'=>'à', 'e'=>'è', 'i'=>'ì', 'o'=>'ò', 'u'=>'ù', ' '=>'`', 'A'=>'À', 'E'=>'È', 'I'=>'Ì', 'O'=>'Ò', 'U'=>'Ù'}, '´' => {'a'=>'á', 'e'=>'é', 'i'=>'í', 'o'=>'ó', 'u'=>'ú', 'y'=>'ý', ' '=>'´', 'A'=>'Á', 'E'=>'É', 'I'=>'Í', 'O'=>'Ó', 'U'=>'Ú', 'Y'=>'Ý'}, '^' => {'a'=>'â', 'e'=>'ê', 'i'=>'î', 'o'=>'ô', 'u'=>'û', ' '=>'^', 'A'=>'Â', 'E'=>'Ê', 'I'=>'Î', 'O'=>'Ô', 'U'=>'Û'}, '¨' => {'a'=>'ä', 'e'=>'ë', 'i'=>'ï', 'o'=>'ö', 'u'=>'ü', 'y'=>'ÿ', ' '=>'¨', 'A'=>'Ä', 'E'=>'Ë', 'I'=>'Ï', 'O'=>'Ö', 'U'=>'Ü', 'y'=>'Ÿ'}, '~' => {'a'=>'ã', 'o'=>'õ', 'n'=>'ñ', ' '=>'~', 'A'=>'Ã', 'O'=>'Õ', 'N'=>'Ñ'}, } def initialize() super(256); @buffer = "\0"*256 end def get_push(i) @enabled and @buffer.getbyte(i)[7] == 1 end def get_toggle(i) @enabled and @buffer.getbyte(i)[0] == 1 end def get_key_name(i) i.between?(0, 255) ? KEYS_NAME[i].dup : '' end def key_name(k) get_key_name(k2i(k)) end def push!(*a) set_state(a, true) end def release!(*a) set_state(a, false) end def toggle!(*a) set_state(a, true); set_state(a, false) end def text_entry() start_text_entry unless @text_entry; @text_entry.dup end def start_text_entry() (@text_entry = ''; setup(@keys)) unless @text_entry end def stop_text_entry() (@text_entry = nil; setup(@user_keys)) if @text_entry end def swap_mouse_button(b) MouseL.i, MouseR.i = b ? 2 : 1, b ? 1 : 2 end def setup(*a) @count.fill(0) @keys = keyarrayize(@text_entry ? [@user_keys=a, TEXT_ENTRY_KEYS] : a) end def update return unless @enabled GetKeyboardState.call(@buffer) super update_text_entry if @text_entry end def update_text_entry @text_entry = '' for i in TEXT_KEYS next if !get_repeat(i) or ToUnicode.call(i, 0, @buffer, utf16="\0"*16, 8, 0)==0 j = ToUnicode.call(i, 0, @buffer, utf16, 8, 0) WideCharToMultiByte.call(65001, 0, utf16, 1, utf8="\0"*4, 4, 0, 0) utf8.delete!("\0") if @dead_key a = DEAD_KEYS[@dead_key] @text_entry, @dead_key = (a && a[utf8]) || (@dead_key + utf8) else j == -1 ? @dead_key=utf8 : @text_entry=utf8 end return end end private def set_state(keys, state) keys, inputs = keyarrayize(keys), '' keys.each {|i| inputs << [1,i,0,state ? 0 : 2,0,0].pack('LSSLLLx8')} SendInput.call(keys.size, inputs, 28) end singleton_attach_methods(@o = new) Keys.each {|k| k.o = @o} def self.o() @o end end class Mouse < Device ::Mouse = self ClipCursor = Win32API.new('user32', 'ClipCursor' , 'p' , 'i') createCursor = Win32API.new('user32', 'CreateCursor' , 'iiiiipp', 'i') findWindow = Win32API.new('user32', 'FindWindow' , 'pp' , 'i') GetClientRect = Win32API.new('user32', 'GetClientRect' , 'ip' , 'i') GetCursorPos = Win32API.new('user32', 'GetCursorPos' , 'p' , 'i') GetWindowRect = Win32API.new('user32', 'GetWindowRect' , 'ip' , 'i') MapWindowPoints = Win32API.new('user32', 'MapWindowPoints', 'iipi' , 'i') PeekMessage = Win32API.new('user32', 'PeekMessage' , 'piiii' , 'i') SetClassLong = Win32API.new('user32', 'SetClassLong' , 'iii' , 'i') SetCursorPos = Win32API.new('user32', 'SetCursorPos' , 'ii' , 'i') Keys = Array.new(7) {|i| Mouse_Key.new(i)} Left, Middle, Right, X1, X2, WheelUp, WheelDown = *Keys constants.each {|s| k = const_get(s); k.s = s.to_s if k.is_a?(Key)} HWND = findWindow.call('RGSS Player', 0) BlankCursor = createCursor.call(0, 0, 0, 1, 1, "\xFF", "\x00") Cache = (defined? RPG::Cache) ? RPG::Cache : ::Cache alias click_max ntrigger_max alias click_max= ntrigger_max= alias click_time ntrigger_time alias click_time= ntrigger_time= attr_accessor :drag_enabled, :drag_auto_clear, :drag_start_size attr_reader :cursor, :drag def initialize super(7) @map = @count.map{[]} for k,v in {Left => [:button, Keyboard::MouseL], Middle => [:button, Keyboard::MouseM], Right => [:button, Keyboard::MouseR], X1 => [:button, Keyboard::MouseX1], X2 => [:button, Keyboard::MouseX2], WheelUp => [:wheel, 1], WheelDown => [:wheel, -1]} @map[k.i] = v end @enabled, @ntrigger_max, @buffer, @keys = false, 3, "\0"*28, (0...7).to_a @drag_enabled, @drag_auto_clear, @drag_start_size = false, false, 10 clip initialize_sprites end def initialize_sprites return if @cursor and !@cursor.disposed? @cursor, @drag = Sprite.new, Sprite.new @cursor.z = @drag.z = 0x7FFFFFFF @cursor.visible, @drag.visible = @enabled, @enabled && @drag_enabled @cursor.bitmap = @default_icon = Bitmap.new(8,8) @cursor.bitmap.fill_rect(0, 0, 3, 7, c=Color.new(0,0,0)) @cursor.bitmap.fill_rect(0, 0, 7, 3, c) @cursor.bitmap.fill_rect(5, 5, 3, 3, c) @cursor.bitmap.fill_rect(1, 1, 1, 5, c.set(255,255,255)) @cursor.bitmap.fill_rect(1, 1, 5, 1, c) @cursor.bitmap.fill_rect(6, 6, 1, 1, c) @drag.bitmap = Bitmap.new(1,1) @drag.bitmap.set_pixel(0, 0, c.set(0,0,255,128)) drag_clear end def enabled=(enabled) initialize_sprites drag_clear unless @enabled = enabled SetClassLong.call(HWND, -12, @enabled ? BlankCursor : 0) @cursor.visible, @drag.visible = @enabled, @enabled && @drag_enabled end def drag_enabled=(drag_enabled) initialize_sprites drag_clear unless @drag_enabled = drag_enabled @drag.visible = @enabled && @drag_enabled end def update return unless @enabled super initialize_sprites update_cursor update_drag update_clip update_wheel end def update_cursor GetCursorPos.call(@buffer) MapWindowPoints.call(0, HWND, @buffer, 1) @cursor.update @cursor.x, @cursor.y = *@buffer.unpack('ll') end def update_drag return unless @drag_enabled @drag.update if Left.trigger? drag_clear @drag_start_point = [@cursor.x, @cursor.y] elsif @drag_start_point and Left.press? x, y = *@drag_start_point w, h = @cursor.x-x, @cursor.y-y if w == 0 then w = 1 elsif w < 0 then x -= w *= -1 end if h == 0 then h = 1 elsif h < 0 then y -= h *= -1 end if dragging? or w > @drag_start_size or h > @drag_start_size @drag.x, @drag.y, @drag.zoom_x, @drag.zoom_y = x, y, w, h end elsif @drag_auto_clear and Left.release? drag_clear end end def update_clip ClipCursor.call(@buffer) if case @clip when String; MapWindowPoints.call(HWND, 0, @buffer.replace(@clip), 2) when 1; GetWindowRect.call(HWND, @buffer) when 2; GetClientRect.call(HWND, @buffer) MapWindowPoints.call(HWND, 0, @buffer, 2) end end def update_wheel @wheel_state = PeekMessage.call(@buffer,HWND,0x020A,0x020A,0)>0 ? @buffer.getbyte(11)==0 ? 1 : -1 : 0 end def on?(x=nil, y=nil, w=nil, h=nil) if !@enabled; false elsif h; @cursor.x.between?(x, x+w) and @cursor.y.between?(y, y+h) elsif w; Math.hypot(x-@cursor.x, y-@cursor.y) <= w elsif y; @cursor.x == x and @cursor.y == y elsif x; on?(x.x, x.y, x.width, x.height) else GetClientRect.call(HWND, @buffer); on?(*@buffer.unpack('l4')) end end def drag_on?(x, y=nil, w=nil, h=nil) if !@enabled or !@drag_enabled; false elsif h x < @drag.x+drag_width and @drag.x < x+w and y < @drag.y+drag_height and @drag.y < y+h elsif w sw, sh = drag_width/2, drag_height/2 x, y = (x-@drag.x-sw).abs, (y-@drag.y-sh).abs x<=sw+w and y<=sh+w and (x<=sw or y<=sh or Math.hypot(x-sw,y-sh)<=w) elsif y x.between?(@drag.x, @drag.x+drag_width) and y.between?(@drag.y, @drag.y+drag_height) else drag_on?(x.x, x.y, x.width, x.height) end end def icon(s=nil, ox=0, oy=0) @cursor.bitmap, @cursor.ox, @cursor.oy = s ? Cache.picture(s) : @default_icon, ox, oy end def clip(x=0, y=nil, w=0, h=0) ClipCursor.call(0) if x.is_a?(Rect); clip(x.x, x.y, x.width, x.height) else @clip = y ? [x, y, w+x, h+y].pack('l4x12') : x end end def click?(k=Left) get_ntrigger(1, k2i(k)) end def dclick?(k=Left) get_ntrigger(2, k2i(k)) end def tclick?(k=Left) get_ntrigger(3, k2i(k)) end def swap_button(b) Keyboard.swap_mouse_button(b) end def x() @cursor.x end def x=(x) set_pos(x, y) end def y() @cursor.y end def y=(y) set_pos(x, y) end def dragging?() @drag.zoom_x != 0 end def drag_x() @drag.x end def drag_y() @drag.y end def drag_width() @drag.zoom_x.to_i end def drag_height() @drag.zoom_y.to_i end def drag_rect() Rect.new(drag_x, drag_y, drag_width, drag_height) end def drag_clear @drag_start_point = nil @drag.x = @drag.y = @drag.zoom_x = @drag.zoom_y = 0 end def get_push(i) return false unless @enabled j, k = *@map[i] case j when :button; k.push? when :wheel ; k == @wheel_state else false end end def get_toggle(i) return false unless @enabled j, k = *@map[i] case j when :button; k.toggle? else false end end private def set_pos(x, y) @cursor.x, @cursor.y = x, y MapWindowPoints.call(HWND, 0, s=[x,y].pack('ll'), 1) SetCursorPos.call(*s.unpack('ll')) end singleton_attach_methods(@o = new) Keys.each {|k| k.o = @o} def self.o() @o end end class Text_Entry_Box < Sprite ::Text_Entry_Box = self SPECIAL_CHARS_CASE = { 'à'=>'À', 'è'=>'È', 'ì'=>'Ì', 'ò'=>'Ò', 'ù'=>'Ù', 'á'=>'Á', 'é'=>'É', 'í'=>'Í', 'ó'=>'Ó', 'ú'=>'Ú', 'ý'=>'Ý', 'â'=>'Â', 'ê'=>'Ê', 'î'=>'Î', 'ô'=>'Ô', 'û'=>'Û', 'ä'=>'Ä', 'ë'=>'Ë', 'ï'=>'Ï', 'ö'=>'Ö', 'ü'=>'Ü', 'ÿ'=>'Ÿ', 'ã'=>'Ã', 'õ'=>'Õ', 'ñ'=>'Ñ', } def self.initialize @@boxes, @@last_icon = [], nil @@icon = [Bitmap.new(9,20), 4, 0] # [bmp, ox, oy] @@icon[0].fill_rect(0, 0, 9, 3, c=Color.new(0,0,0)) @@icon[0].fill_rect(0, 17, 9, 3, c) @@icon[0].fill_rect(3, 3, 3, 14, c) @@icon[0].fill_rect(1, 1, 3, 1, c.set(255,255,255)) @@icon[0].fill_rect(5, 1, 3, 1, c) @@icon[0].fill_rect(1, 18, 3, 1, c) @@icon[0].fill_rect(5, 18, 3, 1, c) @@icon[0].fill_rect(4, 2, 1, 16, c) end initialize attr_accessor :enabled, :mouse, :focus, :back_color, :select_color, :text, :allow_c, :select, :case, :size_max, :width_max def initialize(width, height, viewport=nil) super(viewport) @back_color, @select_color = Color.new(0,0,0,0), Color.new(0,0,255,128) @text, @text_width, @allow_c = '', [], '' @enabled = @mouse = @select = true @case = @size_max = @width_max = @pos = @sel = @off = 0 @text_chars = [] if RUBY_VERSION == '1.8.1' width = 640 if width > 640 and RUBY_VERSION == '1.9.2' self.bitmap = Bitmap.new(width, height) self.class.initialize if @@icon[0].disposed? @@boxes.delete_if {|s| s.disposed?} @focus = @@boxes.empty? @@boxes << self end def width() bitmap.width end def height() bitmap.height end def font() bitmap.font end def font=(f) bitmap.font = f end def hover?() Mouse.on?(x-ox, y-oy, width, height) end def focus!() @@boxes.each {|s| s.focus = false}; @focus = true end def dispose super @@boxes.delete_if {|s| s.disposed?} Keyboard.stop_text_entry bitmap.dispose end def update super return unless @enabled update_mouse if @mouse return unless @focus if update_text_entry refresh elsif @mouse and (Mouse::WheelDown.press? or Mouse::WheelUp.press?) @off = Mouse::WheelDown.press? ? [@off-16, 0].max : [@off+16, text_width(0, size)+1-width].min draw_text_box elsif Graphics.frame_count % 20 == 0 draw_cursor(Graphics.frame_count % 40 == 0) end end def refresh tag = "#{font.name}#{font.size}#{font.bold}#{font.italic}" self.text, @last_font = @text, tag if @last_font != tag min = [text_width(0,@pos)+16-width, text_width(0,size)+1-width].min max = [text_width(0,@pos)-16, 0].max @off = [min, [@off, max].min].max @sel = 0 unless @select draw_text_box end private def update_mouse if hover = hover? and !@@last_icon @@last_icon = [Mouse.cursor.bitmap, Mouse.cursor.ox, Mouse.cursor.oy] Mouse.cursor.bitmap, Mouse.cursor.ox, Mouse.cursor.oy = *@@icon elsif @last_hover != hover and !@last_hover = hover and @@last_icon Mouse.cursor.bitmap, Mouse.cursor.ox, Mouse.cursor.oy = *@@last_icon @@last_icon = nil end if @mouse_select @sel, @mouse_select = @sel+@pos, Mouse::Left.press? @sel -= @pos = get_pos(Mouse.x-x+ox+@off, true) elsif Mouse::Left.trigger? and hover @pos, @sel, @mouse_select = get_pos(Mouse.x-x+ox+@off, true), 0, true Mouse.drag_clear focus! end end def update_text_entry if @mouse_select return false if @last_pos == @pos and @last_sel == @sel @last_pos, @last_sel = @pos, @sel elsif @sel != 0 and (Keyboard::Back.trigger? or Keyboard::Delete.trigger?) @pos -= @sel *= -1 if @sel < 0 self[@pos, @sel], @sel = '', 0 elsif @pos != 0 and Keyboard::Back.repeat? self[@pos -= 1, 1] = '' elsif @pos != size and Keyboard::Delete.repeat? self[@pos, 1] = '' elsif @pos != 0 and Keyboard::Up.trigger? @sel, @pos = @select && Keyboard::Shift.push? ? @sel+@pos : 0, 0 elsif @pos != size and Keyboard::Down.trigger? @sel, @pos = @select && Keyboard::Shift.push? ? @sel+@pos-size : 0, size elsif @pos != 0 and Keyboard::Left.repeat? @pos, @sel = @pos-1, @select && Keyboard::Shift.push? ? @sel+1 : 0 elsif @pos != size and Keyboard::Right.repeat? @pos, @sel = @pos+1, @select && Keyboard::Shift.push? ? @sel-1 : 0 elsif !Keyboard.text_entry.empty? need_refresh, allowed_chars = false, @case==1 ? downcase(@allow_c) : @case==2 ? upcase(@allow_c) : @allow_c for c in Keyboard.text_entry.split(//) break if @size_max != 0 and @size_max <= size c = @case==1 ? downcase(c) : @case==2 ? upcase(c) : c next unless allowed_chars.empty? or allowed_chars.include?(c) _text, _pos, _sel = @text.dup, @pos, @sel if @width_max != 0 @pos -= @sel *= -1 if @sel < 0 self[@pos, @sel], @pos, @sel = c, @pos+1, 0 if @width_max != 0 and text_width(0, size) > @width_max self.text, @pos, @sel = _text, _pos, _sel break end need_refresh = true end return need_refresh else return false end return true end def draw_text_box bitmap.fill_rect(bitmap.rect, @back_color) draw_selection if @sel != 0 draw_text_entry draw_cursor(true) end def draw_selection pos, sel = @sel < 0 ? @pos+@sel : @pos, @sel.abs x, w, h = text_width(0,pos)-@off, text_width(pos,sel), font.size+2 bitmap.fill_rect(x, (height-h)/2, w, h, @select_color) end def draw_cursor(blink) color = blink ? font.color : @back_color x, h = text_width(0, @pos)-@off, font.size+2 bitmap.fill_rect(x, (height-h)/2, 1, h, color) end def draw_text_entry() bitmap.draw_text(-@off, 0, 0xFFFF, height, @text) end def downcase(str) str = str.downcase SPECIAL_CHARS_CASE.each {|d,u| str.gsub!(u, d)} str end def upcase(str) str = str.upcase SPECIAL_CHARS_CASE.each {|d,u| str.gsub!(d, u)} str end def text_width(i, j) @text_width[i] ||= bitmap.text_size(self[0, i]).width @text_width[i+j] ||= bitmap.text_size(self[0, i+j]).width @text_width[i+j] - @text_width[i] end def get_pos(x, round=false) return 0 if x <= 0 return size if x >= text_width(0, size) size.times do |i| if (w = text_width(0,i+1)) > x return i unless round w -= text_width(i,1) / 2 return w > x ? i : i+1 end end end if RUBY_VERSION == '1.9.2' alias normal_draw_text_entry draw_text_entry def draw_text_entry if text_width(0, size) > 640 a, b = get_pos(@off), get_pos(@off+width) b += 1 if b != size and text_width(a, b+1-a) <= 640 a += 1 if text_width(a, b-a) > 640 x = text_width(0,a)-@off bitmap.draw_text(x, 0, 0xFFFF, height, self[a, b-a]) else normal_draw_text_entry end end def [](i, j) @text[i, j] end def []=(i, j, str) @text[i, j] = str @text_width[i, @text_width.size-i] = Array.new(size-i) end public def size() @text.size end def text=(str) str = @case==1 ? downcase(str) : @case==2 ? upcase(str) : str @text_width = Array.new(str.size) @text = str end else def [](i, j) @text_chars[i, j].join('') end def []=(i, j, str) @text_chars[i, j] = str.empty? ? nil : str @text_width[i, @text_width.size-i] = Array.new(size-i) @text.replace(@text_chars.join('')) end public def size() @text_chars.size end def text=(str) str = @case==1 ? downcase(str) : @case==2 ? upcase(str) : str @text_chars.replace(str.split(//)) @text_width = Array.new(@text_chars.size) @text = str end end end def self.[](i) Players[i] end def self.update Keyboard.update Mouse.update Gamepads.each {|g| g.update} Players.each {|p| p.update} end def self.refresh Gamepads.clear x360_pads = XBox360_Gamepad::DLL ? Array.new(4) {|i| XBox360_Gamepad.new(i)} : [] x360_pads.sort! {|a,b| (a.unplugged ? 1 : 0) <=> (b.unplugged ? 1 : 0)} devcaps = "\0"*404 16.times do |i| Multimedia_Gamepad::JoyGetDevCaps.call(i, devcaps, 404) mid = devcaps.unpack('S')[0] if mid == 1118 and !x360_pads.empty? Gamepads << x360_pads.shift elsif mid != 0 Gamepads << Multimedia_Gamepad.new(i) end end Gamepads.sort! {|a,b| (a.unplugged ? 1 : 0) <=> (b.unplugged ? 1 : 0)} Gamepads.each {|g| g.enabled = false} Players.each {|p| p.gamepad_id += 0} end # Le rythme de la fonction repeat? quand on maintient une touche appuyée. # La fonction repeat? est utilisée dans les menus pour bouger le curseur par exemple. f = Graphics.frame_rate REPEAT = [ # (se lit de bas en haut) #[f*4, 1], # Après 4 secondes retourne toujours true. #[f*2, f*0.05], # Après 2 secondes retourne true toutes les 0.05 secondes. [f*0.4, f*0.1], # Après 0.4 secondes retourne true toutes les 0.1 secondes. [2, 0], # ...puis retourne false (à partir de la 2ème frame). [1, 1] # Retourne true quand on vient d'appuyer (la 1ère frame)... ] PLAYERS_MAX = 1 # Le nombre de joueurs pour les jeux multijoueurs. KEYS_MAX = 30 # Nombre de touches, on peut mettre un très grand nombre si on # veut mais le limiter au minimum sert à optimiser la mémoire. Gamepads, Players = [], Array.new(PLAYERS_MAX) {|i| Player.new(i)} singleton_attach_methods(Players[0]) class Player Keys = Array.new(KEYS_MAX) {|i| Player_Key.new(i)} # Les différentes touches du jeu (id entre 0 et KEYS_MAX-1). DOWN = Keys[2] LEFT = Keys[4] RIGHT = Keys[6] UP = Keys[8] A = Keys[11] B = Keys[12] C = Keys[13] X = Keys[14] Y = Keys[15] Z = Keys[16] L = Keys[17] R = Keys[18] SHIFT = Keys[21] CTRL = Keys[22] ALT = Keys[23] F5 = Keys[25] F6 = Keys[26] F7 = Keys[27] F8 = Keys[28] F9 = Keys[29] constants.each {|s| Input.const_set(s,k=const_get(s)); k.s = s.to_s if k.is_a?(Key)} end # La config par défaut des touches. # En cas de jeu multijoueur il en faut une différente pour chaque joueur. # Liste des touches du clavier ligne 862. # Liste des touches XBox360 ligne 689, pour manette random ligne 681. # Mais inutile de mettre les deux, par exemple :A et :Button1 sont la même # touche, si moi j'utilise celles d'XBox c'est juste parce que ça me semble # plus logique de régler les touches par rapport à une config connue. # Le mieux étant de faire un menu où le joueur peut configurer lui même tout ça. # Le format est très simple, à gauche les touches virtuelles du jeu, à droite # un tableau des touches correspondantes séparé en deux, 1ère colonne les # touches de la manette, 2ème colonne les touches du clavier. # Pour les jeux multijoueurs la config des manettes se fait pareillement pour # tous, la différenciation des manettes se fait en interne. # Config du joueur 1 : Players[0].setup( :DOWN => [ [:AxisLY_0, :DPadDown] , [:Down] ], :LEFT => [ [:AxisLX_0, :DPadLeft] , [:Left] ], :RIGHT => [ [:AxisLX_1, :DPadRight], [:Right] ], :UP => [ [:AxisLY_1, :DPadUp] , [:Up] ], :A => [ [:X] , [:Z, :Shift] ], :B => [ [:Y] , [:X, :NumPad0, :Escape] ], :C => [ [:A] , [:C, :Enter, :Space] ], :X => [ [:B] , [:A] ], :Y => [ [:LT] , [:S] ], :Z => [ [:RT] , [:D] ], :L => [ [:LB] , [:Q, :PageUp] ], :R => [ [:RB] , [:W, :PageDown] ], :SHIFT => [ [] , [:Shift] ], :CTRL => [ [] , [:Control] ], :ALT => [ [] , [:Alt] ], :F5 => [ [] , [:F5] ], :F6 => [ [] , [:F6] ], :F7 => [ [] , [:F7] ], :F8 => [ [] , [:F8] ], :F9 => [ [] , [:F9] ]) refresh update Keyboard.release!(0...256) if Keyboard.push?(*0...256) end