• R/O
  • HTTP
  • SSH
  • HTTPS

提交

標籤
無標籤

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Ruby GTK3移行後のメインリポジトリ


Commit MetaInfo

修訂6cd39a019ced21e5ead03056f031ef9f01f1078b (tree)
時間2014-05-06 22:50:06
作者Shyouzou Sugitani <shy@user...>
CommiterShyouzou Sugitani

Log Message

add nekodorif.rb

Change Summary

差異

--- a/lib/ninix/communicate.rb
+++ b/lib/ninix/communicate.rb
@@ -1,6 +1,6 @@
11 # -*- coding: utf-8 -*-
22 #
3-# communicate.py - ghost-to-ghost communication mechanism
3+# communicate.rb - ghost-to-ghost communication mechanism
44 # Copyright (C) 2002-2014 by Shyouzou Sugitani <shy@users.sourceforge.jp>
55 # Copyright (C) 2002, 2003 by MATSUMURA Namihiko <nie@counterghost.net>
66 #
--- /dev/null
+++ b/lib/ninix/nekodorif.rb
@@ -0,0 +1,957 @@
1+# -*- coding: utf-8 -*-
2+#
3+# Copyright (C) 2004-2014 by Shyouzou Sugitani <shy@users.sourceforge.jp>
4+#
5+# This program is free software; you can redistribute it and/or modify it
6+# under the terms of the GNU General Public License (version 2) as
7+# published by the Free Software Foundation. It is distributed in the
8+# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
9+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10+# PURPOSE. See the GNU General Public License for more details.
11+#
12+
13+# TODO:
14+# - 「きのこ」へのステータス送信.
15+# - 「きのこ」の情報の参照.
16+# - SERIKO/1.2ベースのアニメーション
17+# - (スキン側の)katochan.txt
18+# - balloon.txt
19+# - surface[0/1/2]a.txt(@ゴースト)
20+# - 自爆イベント
21+# - headrect.txt : 頭の当たり判定領域データ
22+# 当たり領域のleft/top/right/bottomを半角カンマでセパレートして記述.
23+# 1行目がsurface0、2行目がsurface1の領域データ.
24+# このファイルがない場合、領域は自動計算される.
25+# - speak.txt
26+# - katochan が無い場合の処理.(本体の方のpopup menuなども含めて)
27+# - 設定ダイアログ : [会話/反応]タブ -> [SEND SSTP/1.1] or [SHIORI]
28+# - 見切れ連続20[s]、もしくは画面内で静止20[s]でアニメーション記述ミスと見なし自動的に落ちる
29+# - 発言中にバルーンをダブルクリックで即閉じ
30+# - @ゴースト名は#nameと#forには使えない. もし書いても無視されすべて有効になる
31+# - 連続落し不可指定
32+# チェックしておくと落下物を2個以上同時に落とせなくなる
33+# - スキンチェンジ時も起動時のトークを行う
34+# - ファイルセット設定機能
35+# インストールされたスキン/落下物のうち使用するものだけを選択できる
36+# - ターゲットのアイコン化への対応
37+# - アイコン化されているときは自動落下しない
38+# - アイコン化されているときのDirectSSTP SEND/DROPリクエストはエラー(Invisible)
39+# - 落下物の透明化ON/OFF
40+# - 落下物が猫どりふ自身にも落ちてくる
41+# 不在時に1/2、ランダム/全員落し時に1/10の確率で自爆
42+# - 一定時間間隔で勝手に物を落とす
43+# - ターゲット指定落し、ランダム落し、全員落し
44+# - 出現即ヒットの場合への対応
45+
46+# - 複数ゴーストでの当たり判定.
47+# - 透明ウィンドウ
48+
49+require "gettext"
50+require "gtk3"
51+
52+require "ninix/pix"
53+require "ninix/home"
54+
55+module Nekodorif
56+
57+ class Menu
58+ include GetText
59+
60+ bindtextdomain("ninix-aya")
61+
62+ def initialize(accelgroup)
63+ @parent = nil
64+ ui_info = <<-EOS
65+ <ui>
66+ <popup name='popup'>
67+ <menuitem action='Settings'/>
68+ <menu action='Katochan'>
69+ </menu>
70+ <separator/>
71+ <menuitem action='Exit'/>
72+ </popup>
73+ </ui>
74+ EOS
75+ @__menu_list = {
76+ 'settings' => [['Settings', nil, _('Settings...(_O)'), nil,
77+ '', lambda {|a, b| @parent.handle_request('NOTIFY', 'edit_preferences')}],
78+ '/ui/popup/Settings'],
79+ 'katochan' => [['Katochan', nil, _('Katochan(_K)'), nil],
80+ '/ui/popup/Katochan'],
81+ 'exit' => [['Exit', nil,_('Exit(_Q)'), nil,
82+ '', lambda {|a, b| @parent.handle_request('NOTIFY', 'close')}],
83+ '/ui/popup/Exit'],
84+ }
85+ @__katochan_list = nil
86+ actions = Gtk::ActionGroup.new('Actions')
87+ entry = []
88+ for value in @__menu_list.values()
89+ entry << value[0]
90+ end
91+ actions.add_actions(entry)
92+ ui_manager = Gtk::UIManager.new()
93+ ui_manager.insert_action_group(actions, 0)
94+ ui_manager.add_ui(ui_info)
95+ @__popup_menu = ui_manager.get_widget('/ui/popup')
96+ for key in @__menu_list.keys
97+ path = @__menu_list[key][-1]
98+ @__menu_list[key][1] = ui_manager.get_widget(path)
99+ end
100+ end
101+
102+ def set_responsible(parent)
103+ @parent = parent
104+ end
105+
106+ def popup(button)
107+ katochan_list = @parent.handle_request('GET', 'get_katochan_list')
108+ __set_katochan_menu(katochan_list)
109+ @__popup_menu.popup(nil, nil, button, Gtk.current_event_time)
110+ end
111+
112+ def __set_katochan_menu(list) ## FIXME
113+ key = 'katochan'
114+ if not list.empty?
115+ menu = Gtk::Menu.new()
116+ for katochan in list
117+ item = Gtk::MenuItem.new(katochan['name'])
118+ item.signal_connect('activate', katochan) do |a, k|
119+ @parent.handle_request('NOTIFY', 'select_katochan', k)
120+ end
121+ menu.add(item)
122+ item.show()
123+ end
124+ @__menu_list[key][1].set_submenu(menu)
125+ menu.show()
126+ @__menu_list[key][1].show()
127+ else
128+ @__menu_list[key][1].hide()
129+ end
130+ end
131+ end
132+
133+ class Nekoninni
134+
135+ def initialize
136+ @mode = 1 # 0: SEND SSTP1.1, 1: SHIORI/2.2
137+ @__running = false
138+ @skin = nil
139+ @katochan = nil
140+ end
141+
142+ def observer_update(event, args)
143+ if ['set position', 'set surface'].include?(event)
144+ if @skin != nil
145+ @skin.set_position()
146+ end
147+ if @katochan != nil and @katochan.loaded
148+ @katochan.set_position()
149+ end
150+ elsif event == 'set scale'
151+ scale = @target.get_surface_scale()
152+ if @skin != nil
153+ @skin.set_scale(scale)
154+ end
155+ if @katochan != nil
156+ @katochan.set_scale(scale)
157+ end
158+ elsif event == 'finalize'
159+ finalize()
160+ else
161+ ##logging.debug('OBSERVER(nekodorif): ignore - {0}'.format(event))
162+ end
163+ end
164+
165+ def load(dir, katochan, target)
166+ if katochan.empty?
167+ return 0
168+ end
169+ @dir = dir
170+ @target = target
171+ @target.attach_observer(self)
172+ @accelgroup = Gtk::AccelGroup.new()
173+ scale = @target.get_surface_scale()
174+ @skin = Skin.new(@dir, @accelgroup, scale)
175+ @skin.set_responsible(self)
176+ if @skin == nil
177+ return 0
178+ end
179+ @katochan_list = katochan
180+ @katochan = nil
181+ launch_katochan(@katochan_list[0])
182+ @__running = true
183+ GLib::Timeout.add(50) { do_idle_tasks } # 50[ms]
184+ return 1
185+ end
186+
187+ def handle_request(event_type, event, *arglist, **argdict)
188+ ##assert ['GET', 'NOTIFY'].include?(event_type)
189+ handlers = {
190+ 'get_katochan_list' => lambda { return @katochan_list },
191+ 'get_mode' => lambda { return @mode },
192+ }
193+# handler = handlers.get(event,
194+# getattr(self, event,
195+# lambda {|a| return nil})) ## FIXME
196+# result = handler(*arglist, **argdict)
197+# if not handlers.include?(event)
198+# result = @parent.handle_request(event_type, event, *arglist, **argdict)
199+# else
200+ if handlers.include?(event)
201+ result = handlers[event].call #( *arglist, **argdict)
202+ else
203+ ## FIXME
204+ print("REQ: ", Nekoninni.method_defined?(event), " , ", *arglist, "\n")
205+ result = method(event).call(*arglist)#, **argdict)
206+ end
207+ if event_type == 'GET'
208+ return result
209+ end
210+ end
211+
212+ def do_idle_tasks
213+ if not @__running
214+ return false
215+ end
216+ @skin.update()
217+ if @katochan != nil
218+ @katochan.update()
219+ end
220+ #self.process_script()
221+ return true
222+ end
223+
224+ def send_event(event)
225+ if not ['Emerge', # 可視領域内に出現
226+ 'Hit', # ヒット
227+ 'Drop', # 再落下開始
228+ 'Vanish', # ヒットした落下物が可視領域内から消滅
229+ 'Dodge' # よけられてヒットしなかった落下物が可視領域内から消滅
230+ ].include?(event)
231+ return
232+ end
233+ args = [@katochan.get_name(),
234+ @katochan.get_ghost_name(),
235+ @katochan.get_category(),
236+ @katochan.get_kinoko_flag(),
237+ @katochan.get_target()]
238+ @target.notify_event('OnNekodorifObject' + event.to_s, *args)
239+ end
240+
241+ def has_katochan
242+ if @katochan != nil
243+ return true
244+ else
245+ return false
246+ end
247+ end
248+
249+ def select_katochan(args)
250+ launch_katochan(args)
251+ end
252+
253+ def drop_katochan
254+ @katochan.drop()
255+ end
256+
257+ def delete_katochan
258+ @katochan.destroy()
259+ @katochan = nil
260+ @skin.reset()
261+ end
262+
263+ def launch_katochan(katochan)
264+ if @katochan
265+ @katochan.destroy()
266+ end
267+ @katochan = Katochan.new(@target)
268+ @katochan.set_responsible(self)
269+ @katochan.load(katochan)
270+ end
271+
272+ def edit_preferences
273+ end
274+
275+ def finalize
276+ @__running = false
277+ @target.detach_observer(self)
278+ if @katochan != nil
279+ @katochan.destroy()
280+ end
281+ if @skin != nil
282+ @skin.destroy()
283+ end
284+ ##if self.balloon is not None:
285+ ## self.balloon.destroy()
286+ end
287+
288+ def close
289+ finalize()
290+ end
291+ end
292+
293+ class Skin
294+
295+ def initialize(dir, accelgroup, scale)
296+ @dir = dir
297+ @accelgroup = accelgroup
298+ @parent = nil
299+ @dragged = false
300+ @x_root = nil
301+ @y_root = nil
302+ @__scale = scale
303+ @__menu = Menu.new(@accelgroup)
304+ @__menu.set_responsible(self)
305+ path = File.join(@dir, 'omni.txt')
306+ if File.file?(path) and File.size(path) == 0
307+ @omni = 1
308+ else
309+ @omni = 0
310+ end
311+ @window = Pix::TransparentWindow.new()
312+ name, top_dir = Home.read_profile_txt(dir) # XXX
313+ @window.set_title(name)
314+ @window.signal_connect('delete_event') do |w, e|
315+ delete(w, e)
316+ end
317+ @window.signal_connect('key_press_event') do |w, e|
318+ key_press(w, e)
319+ end
320+ @window.add_accel_group(@accelgroup)
321+ @darea = @window.darea # @window.get_child()
322+ @darea.set_events(Gdk::Event::EXPOSURE_MASK|
323+ Gdk::Event::BUTTON_PRESS_MASK|
324+ Gdk::Event::BUTTON_RELEASE_MASK|
325+ Gdk::Event::POINTER_MOTION_MASK|
326+ Gdk::Event::POINTER_MOTION_HINT_MASK|
327+ Gdk::Event::LEAVE_NOTIFY_MASK)
328+ @darea.signal_connect('draw') do |w, cr|
329+ redraw(w, cr)
330+ end
331+ @darea.signal_connect('button_press_event') do |w, e|
332+ button_press(w, e)
333+ end
334+ @darea.signal_connect('button_release_event') do |w, e|
335+ button_release(w, e)
336+ end
337+ @darea.signal_connect('motion_notify_event') do |w, e|
338+ motion_notify(w, e)
339+ end
340+ @darea.signal_connect('leave_notify_event') do |w, e|
341+ leave_notify(w, e)
342+ end
343+ @id = [0, nil]
344+ set_surface()
345+ set_position(reset=1)
346+ @window.show_all()
347+ end
348+
349+ def set_responsible(parent)
350+ @parent = parent
351+ end
352+
353+ def handle_request(event_type, event, *arglist, **argdict)
354+ ##assert ['GET', 'NOTIFY'].include?(event_type)
355+ handlers = {
356+ }
357+# handler = handlers.get(event, getattr(self, event, nil))
358+# if handler == nil
359+ if not handlers.include?(event)
360+ result = @parent.handle_request(event_type, event, *arglist, **argdict)
361+ else
362+ result = method(event).call(*arglist)#, **argdict)
363+ end
364+ if event_type == 'GET'
365+ return result
366+ end
367+ end
368+
369+ def set_scale(scale)
370+ @__scale = scale
371+ set_surface()
372+ set_position()
373+ end
374+
375+ def redraw(widget, cr)
376+ scale = @__scale
377+ cr.scale(scale / 100.0, scale / 100.0)
378+ cr.set_source(@image_surface, 0, 0)
379+ cr.set_operator(Cairo::OPERATOR_SOURCE)
380+ cr.paint()
381+ end
382+
383+ def delete()#widget, event)
384+ @parent.handle_request('NOTIFY', 'finalize')
385+ end
386+
387+ def key_press(window, event)
388+ if event.state & (Gdk::Window::ModifierType::CONTROL_MASK | Gdk::Window::ModifierType::SHIFT_MASK)
389+ if event.keyval == Gdk::Keyval::GDK_KEY_F12
390+ #logging.info('reset skin position')
391+ set_position(reset=1)
392+ end
393+ end
394+ return true
395+ end
396+
397+ def destroy ## FIXME
398+ @window.destroy()
399+ end
400+
401+ def button_press(widget, event)
402+ @x_root = event.x_root
403+ @y_root = event.y_root
404+ if event.button == 1
405+ if event.event_type == Gdk::Event::BUTTON_PRESS
406+ #pass
407+ elsif event.event_type == Gdk::Event::DOUBLE_BUTTON_PRESS # double click
408+ if @parent.handle_request('GET', 'has_katochan')
409+ start() ## FIXME
410+ @parent.handle_request('NOTIFY', 'drop_katochan')
411+ end
412+ end
413+ elsif event.button == 3
414+ if event.event_type == Gdk::Event::BUTTON_PRESS
415+ @__menu.popup(event.button)
416+ end
417+ end
418+ return true
419+ end
420+
421+ def set_surface
422+ if @id[1] != nil
423+ path = File.join(@dir, 'surface' + @id[0].to_s + @id[1].to_s + '.png')
424+ if not File.exists?(path)
425+ @id[1] = nil
426+ set_surface()
427+ return
428+ end
429+ else
430+ path = File.join(@dir, 'surface' + @id[0].to_s + '.png')
431+ end
432+ begin
433+ new_surface = Pix.create_surface_from_file(path)
434+ w = [8, (new_surface.width * @__scale / 100).to_i].max
435+ h = [8, (new_surface.height * @__scale / 100).to_i].max
436+ rescue # except:
437+ @parent.handle_request('NOTIFY', 'finalize')
438+ return
439+ end
440+ @w, @h = w, h
441+ @window.update_size(@w, @h)
442+ @image_surface = new_surface
443+ @darea.queue_draw()
444+ end
445+
446+ def set_position(reset=0)
447+ left, top, scrn_w, scrn_h = Pix.get_workarea()
448+ if reset
449+ @x = left
450+ @y = top + scrn_h - @h
451+ else
452+ if not @omni
453+ @y = top + scrn_h - @h
454+ end
455+ end
456+ @window.move(@x, @y)
457+ end
458+
459+ def move(x_delta, y_delta)
460+ @x = @x + x_delta
461+ if @omni
462+ @y = @y + y_delta
463+ end
464+ set_position()
465+ end
466+
467+ def update
468+ if @id[1] != nil
469+ @id[1] += 1
470+ else
471+ if Random.rand(0..99) != 0 ## XXX
472+ return
473+ end
474+ @id[1] = 0
475+ end
476+ set_surface()
477+ end
478+
479+ def start ## FIXME
480+ @id[0] = 1
481+ set_surface()
482+ end
483+
484+ def reset ## FIXME
485+ @id[0] = 0
486+ set_surface()
487+ end
488+
489+ def button_release(widget, event)
490+ if @dragged
491+ @dragged = false
492+ set_position()
493+ end
494+ @x_root = nil
495+ @y_root = nil
496+ return true
497+ end
498+
499+ def motion_notify(widget, event)
500+ if event.hint?
501+ _, x, y, state = widget.window.get_device_position(event.device)
502+ else
503+ x, y, state = event.x, event.y, event.state
504+ end
505+ #x, y = self.window.winpos_to_surfacepos(x, y, self.__scale)
506+ if state & Gdk::Window::ModifierType::BUTTON1_MASK
507+ if @x_root != nil and \
508+ @y_root != nil
509+ @dragged = true
510+ x_delta = (event.x_root - @x_root).to_i
511+ y_delta = (event.y_root - @y_root).to_i
512+ move(x_delta, y_delta)
513+ @x_root = event.x_root
514+ @y_root = event.y_root
515+ end
516+ end
517+ return true
518+ end
519+
520+ def leave_notify(widget, event) ## FIXME
521+ end
522+ end
523+
524+ class Balloon
525+
526+ def initialize
527+ end
528+
529+ def destroy ## FIXME
530+ #pass
531+ end
532+ end
533+
534+ class Katochan
535+
536+ CATEGORY_LIST = ['pain', # 痛い
537+ 'stab', # 刺さる
538+ 'surprise', # びっくり
539+ 'hate', # 嫌い、気持ち悪い
540+ 'huge', # 巨大
541+ 'love', # 好き、うれしい
542+ 'elegant', # 風流、優雅
543+ 'pretty', # かわいい
544+ 'food', # 食品
545+ 'reference', # 見る/読むもの
546+ 'other' # 上記カテゴリに当てはまらないもの
547+ ]
548+
549+ def initialize(target)
550+ @side = 0
551+ @target = target
552+ @parent = nil
553+ @settings = {}
554+ @settings['state'] = 'before'
555+ @settings['fall.type'] = 'gravity'
556+ @settings['fall.speed'] = 1
557+ @settings['slide.type'] = 'none'
558+ @settings['slide.magnitude'] = 0
559+ @settings['slide.sinwave.degspeed'] = 30
560+ @settings['wave'] = nil
561+ @settings['wave.loop'] = 0
562+ @__scale = 100
563+ @loaded = false
564+ end
565+
566+ def set_responsible(parent)
567+ @parent = parent
568+ end
569+
570+ def get_name
571+ return @data['name']
572+ end
573+
574+ def get_category
575+ return @data['category']
576+ end
577+
578+ def get_kinoko_flag ## FIXME
579+ return 0 # 0/1 = きのこに当たっていない(ない場合を含む)/当たった
580+ end
581+
582+ def get_target ## FIXME
583+ if @side == 0
584+ return @target.get_selfname()
585+ else
586+ return @target.get_keroname()
587+ end
588+ end
589+
590+ def get_ghost_name
591+ if @data.include?('for') # 落下物が主に対象としているゴーストの名前
592+ return @data['for']
593+ else
594+ return ''
595+ end
596+ end
597+
598+ def destroy
599+ @window.destroy()
600+ end
601+
602+ def delete()#widget, event)
603+ destroy()
604+ end
605+
606+ def redraw(widget, cr)
607+ scale = @__scale
608+ cr.scale(scale / 100.0, scale / 100.0)
609+ cr.set_source(@image_surface, 0, 0)
610+ cr.set_operator(Cairo::OPERATOR_SOURCE)
611+ cr.paint()
612+ end
613+
614+ def set_movement(timing)
615+ key = timing + 'fall.type'
616+ if @data.include?(key) and \
617+ ['gravity', 'evenspeed', 'none'].include?(@data[key])
618+ @settings['fall.type'] = @data[key]
619+ else
620+ @settings['fall.type'] = 'gravity'
621+ end
622+ if @data.include?(timing + 'fall.speed')
623+ @settings['fall.speed'] = @data[timing + 'fall.speed']
624+ else
625+ @settings['fall.speed'] =1
626+ end
627+ if @settings['fall.speed'] < 1
628+ @settings['fall.speed'] = 1
629+ end
630+ if @settings['fall.speed'] > 100
631+ @settings['fall.speed'] = 100
632+ end
633+ key = timing + 'slide.type'
634+ if @data.include?(key) and \
635+ ['none', 'sinwave', 'leaf'].include?(@data[key])
636+ @settings['slide.type'] = @data[key]
637+ else
638+ @settings['slide.type'] = 'none'
639+ end
640+ if @data.include?(timing + 'slide.magnitude')
641+ @settings['slide.magnitude'] = @data[timing + 'slide.magnitude']
642+ else
643+ @settings['slide.magnitude'] = 0
644+ end
645+ if @data.include?(timing + 'slide.sinwave.degspeed')
646+ @settings['slide.sinwave.degspeed'] = @data[timing + 'slide.sinwave.degspeed']
647+ else
648+ @settings['slide.sinwave.degspeed'] = 30
649+ end
650+ if @data.include?(timing + 'wave')
651+ @settings['wave'] = @data[timing + 'wave']
652+ else
653+ @settings['wave'] = nil
654+ end
655+ if @data.include?(timing + 'wave.loop')
656+ if @data[timing + 'wave.loop'] == 'on'
657+ @settings['wave.loop'] = 1
658+ else
659+ @settings['wave.loop'] = 0
660+ end
661+ else
662+ @settings['wave.loop'] = 0
663+ end
664+ end
665+
666+ def set_scale(scale)
667+ @__scale = scale
668+ set_surface()
669+ set_position()
670+ end
671+
672+ def set_position
673+ if @settings['state'] != 'before'
674+ return
675+ end
676+ target_x, target_y = @target.get_surface_position(@side)
677+ target_w, target_h = @target.get_surface_size(@side)
678+ left, top, scrn_w, scrn_h = Pix.get_workarea()
679+ @x = target_x + target_w / 2 - @w / 2 + (@offset_x * @__scale / 100).to_i
680+ @y = top + (@offset_y * @__scale / 100).to_i
681+ @window.move(@x, @y)
682+ end
683+
684+ def set_surface
685+ path = File.join(@data['dir'], 'surface' + @id.to_s + '.png')
686+ begin
687+ new_surface = Pix.create_surface_from_file(path)
688+ w = [8, (new_surface.width * @__scale / 100).to_i].max
689+ h = [8, (new_surface.height * @__scale / 100).to_i].max
690+ rescue # except:
691+ @parent.handle_request('NOTIFY', 'finalize')
692+ return
693+ end
694+ @w, @h = w, h
695+ @window.update_size(@w, @h)
696+ #self.darea.queue_draw_area(0, 0, self.w, self.h)
697+ @image_surface = new_surface
698+ @darea.queue_draw()
699+ end
700+
701+ def load(data)
702+ @data = data
703+ @__scale = @target.get_surface_scale()
704+ set_state('before')
705+ if @data.include?('category')
706+ category = @data['category'].split(',')
707+ if category
708+ if not CATEGORY_LIST.include?(category[0])
709+ logging.warning('WARNING: unknown major category - {0}'.format(category[0]))
710+ ##self.data['category'] = self.CATEGORY_LIST[-1]
711+ end
712+ else
713+ @data['category'] = CATEGORY_LIST[-1]
714+ end
715+ else
716+ @data['category'] = CATEGORY_LIST[-1]
717+ end
718+ if @data.include?('target')
719+ if @data['target'] == 'sakura'
720+ @side = 0
721+ elsif @data['target'] == 'kero'
722+ @side = 1
723+ else
724+ @side = 0 # XXX
725+ end
726+ else
727+ @side = 0 # XXX
728+ end
729+ if @parent.handle_request('GET', 'get_mode') == 1
730+ @parent.handle_request('NOTIFY', 'send_event', 'Emerge')
731+ else
732+ if @data.include?('before.script')
733+ #pass ## FIXME
734+ else
735+ #pass ## FIXME
736+ end
737+ end
738+ set_movement('before')
739+ if @data.include?('before.appear.direction')
740+ #pass ## FIXME
741+ else
742+ #pass ## FIXME
743+ end
744+ if @data.include?('before.appear.ofset.x')
745+ offset_x = @data['before.appear.ofset.x']
746+ else
747+ offset_x = 0
748+ end
749+ if offset_x < -32768
750+ offset_x = -32768
751+ end
752+ if offset_x > 32767
753+ offset_x = 32767
754+ end
755+ if @data.include?('before.appear.ofset.y')
756+ offset_y = @data['before.appear.ofset.y']
757+ else
758+ offset_y = 0
759+ end
760+ if offset_y < -32768
761+ offset_y = -32768
762+ end
763+ if offset_y > 32767
764+ offset_y = 32767
765+ end
766+ @offset_x = offset_x
767+ @offset_y = offset_y
768+ @window = Pix::TransparentWindow.new()
769+ @window.set_title(@data['name'])
770+ @window.set_skip_taskbar_hint(true) # XXX
771+ @window.signal_connect('delete_event') do |w, e|
772+ delete(w, e)
773+ end
774+ @darea = @window.darea #@window.get_child()
775+ @darea.set_events(Gdk::Event::EXPOSURE_MASK)
776+ @darea.signal_connect('draw') do |w, cr|
777+ redraw(w, cr)
778+ end
779+ @window.show()
780+ @id = 0
781+ set_surface()
782+ set_position()
783+ @loaded = true
784+ end
785+
786+ def drop ## FIXME
787+ set_state('fall')
788+ end
789+
790+ def set_state(state)
791+ @settings['state'] = state
792+ @time = 0
793+ @hit = 0
794+ @hit_stop = 0
795+ end
796+
797+ def update_surface ## FIXME
798+ #pass
799+ end
800+
801+ def update_position ## FIXME
802+ if @settings['slide.type'] == 'leaf'
803+ #pass
804+ else
805+ if @settings['fall.type'] == 'gravity'
806+ @y += @settings['fall.speed'].to_i * \
807+ (@time / 20.0)**2
808+ elsif @settings['fall.type'] == 'evenspeed'
809+ @y += @settings['fall.speed']
810+ else
811+ #pass
812+ end
813+ if @settings['slide.type'] == 'sinwave'
814+ #pass ## FIXME
815+ else
816+ #pass
817+ end
818+ end
819+ @window.move(@x, @y)
820+ end
821+
822+ def check_collision ## FIXME: check self position
823+ for side in [0, 1]
824+ target_x, target_y = @target.get_surface_position(side)
825+ target_w, target_h = @target.get_surface_size(side)
826+ center_x = @x + @w / 2
827+ center_y = @y + @h / 2
828+ if target_x < center_x and center_x < target_x + target_w and \
829+ target_y < center_y and center_y < target_y + target_h
830+ @side = side
831+ return 1
832+ end
833+ end
834+ return 0
835+ end
836+
837+ def check_mikire
838+ left, top, scrn_w, scrn_h = Pix.get_workarea()
839+ if @x + @w - @w / 3 > left + scrn_w or \
840+ @x + @w / 3 < left or \
841+ @y + @h - @h / 3 > top + scrn_h or \
842+ @y + @h / 3 < top
843+ return 1
844+ else
845+ return 0
846+ end
847+ end
848+
849+ def update ## FIXME
850+ if @settings['state'] == 'fall'
851+ update_surface()
852+ update_position()
853+ if check_collision()
854+ set_state('hit')
855+ @hit = 1
856+ if @parent.handle_request('GET', 'get_mode') == 1
857+ @id = 1
858+ set_surface()
859+ @parent.handle_request('NOTIFY', 'send_event', 'Hit')
860+ else
861+ #pass ## FIXME
862+ end
863+ end
864+ if check_mikire()
865+ set_state('dodge')
866+ end
867+ elsif @settings['state'] == 'hit'
868+ if @hit_stop >= @data.get('hit.waittime', 0)
869+ set_state('after')
870+ set_movement('after')
871+ if @parent.handle_request('GET', 'get_mode') == 1
872+ @id = 2
873+ set_surface()
874+ @parent.handle_request('NOTIFY', 'send_event', 'Drop')
875+ else
876+ #pass ## FIXME
877+ end
878+ else
879+ @hit_stop += 1
880+ update_surface()
881+ end
882+ elsif @settings['state'] == 'after'
883+ update_surface()
884+ update_position()
885+ if check_mikire()
886+ set_state('end')
887+ end
888+ elsif @settings['state'] == 'end'
889+ if @parent.handle_request('GET', 'get_mode') == 1
890+ @parent.handle_request('NOTIFY', 'send_event', 'Vanish')
891+ else
892+ #pass ## FIXME
893+ end
894+ @parent.handle_request('NOTIFY', 'delete_katochan')
895+ return false
896+ elsif @settings['state'] == 'dodge'
897+ if @parent.handle_request('GET', 'get_mode') == 1
898+ @parent.handle_request('NOTIFY', 'send_event', 'Dodge')
899+ else
900+ #pass ## FIXME
901+ end
902+ @parent.handle_request('NOTIFY', 'delete_katochan')
903+ return false
904+ else
905+ ## check collision and mikire
906+ end
907+ @time += 1
908+ return true
909+ end
910+ end
911+
912+ class TEST
913+
914+ def initialize
915+ nekoninni = Home.search_nekoninni()
916+ katochan = Home.search_katochan()
917+ neko = Nekoninni.new
918+ ninni = nekoninni.sample
919+ neko.load(ninni[1], katochan, self)
920+ Gtk.main
921+ end
922+
923+ def attach_observer(nekoninni) # dummy
924+ end
925+
926+ def detach_observer(nekoninni) # dummy
927+ end
928+
929+ def get_surface_scale
930+ return 100
931+ end
932+
933+ def get_selfname
934+ return "Sakura"
935+ end
936+
937+ def get_keroname
938+ return "Kero"
939+ end
940+
941+ def notify_event(*a)
942+ end
943+
944+ def get_surface_position(side)
945+ return 0, 0
946+ end
947+
948+ def get_surface_size(side)
949+ return 100, 100
950+ end
951+
952+ def handle_request(type, event, *a) # dummy
953+ end
954+ end
955+end
956+
957+Nekodorif::TEST.new