diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000000..3335045086
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE.txt
similarity index 92%
rename from LICENSE
rename to LICENSE.txt
index 70566f2d0e..e587591e14 100644
--- a/LICENSE
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-GNU GENERAL PUBLIC LICENSE
+ GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
@@ -618,57 +618,4 @@ an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- {one line to give the program's name and a brief idea of what it does.}
- Copyright (C) {year} {name of author}
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- {project} Copyright (C) {year} {fullname}
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
\ No newline at end of file
+ END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/assets/amulet.png b/assets/amulet.png
new file mode 100644
index 0000000000..8ebbac7ccf
Binary files /dev/null and b/assets/amulet.png differ
diff --git a/assets/arcs1.png b/assets/arcs1.png
new file mode 100644
index 0000000000..900b52e137
Binary files /dev/null and b/assets/arcs1.png differ
diff --git a/assets/arcs2.png b/assets/arcs2.png
new file mode 100644
index 0000000000..1c57482827
Binary files /dev/null and b/assets/arcs2.png differ
diff --git a/assets/avatars.png b/assets/avatars.png
new file mode 100644
index 0000000000..3bd332c5ab
Binary files /dev/null and b/assets/avatars.png differ
diff --git a/assets/badges.png b/assets/badges.png
new file mode 100644
index 0000000000..20d380331b
Binary files /dev/null and b/assets/badges.png differ
diff --git a/assets/banners.png b/assets/banners.png
new file mode 100644
index 0000000000..b0a01b7871
Binary files /dev/null and b/assets/banners.png differ
diff --git a/assets/bat.png b/assets/bat.png
new file mode 100644
index 0000000000..d1104c422c
Binary files /dev/null and b/assets/bat.png differ
diff --git a/assets/blacksmith.png b/assets/blacksmith.png
new file mode 100644
index 0000000000..d5e1fd91f8
Binary files /dev/null and b/assets/blacksmith.png differ
diff --git a/assets/brute.png b/assets/brute.png
new file mode 100644
index 0000000000..e014438363
Binary files /dev/null and b/assets/brute.png differ
diff --git a/assets/buffs.png b/assets/buffs.png
new file mode 100644
index 0000000000..7c12ab545d
Binary files /dev/null and b/assets/buffs.png differ
diff --git a/assets/burning_fist.png b/assets/burning_fist.png
new file mode 100644
index 0000000000..ea34c58c06
Binary files /dev/null and b/assets/burning_fist.png differ
diff --git a/assets/chrome.png b/assets/chrome.png
new file mode 100644
index 0000000000..e11048efa1
Binary files /dev/null and b/assets/chrome.png differ
diff --git a/assets/crab.png b/assets/crab.png
new file mode 100644
index 0000000000..f80950592d
Binary files /dev/null and b/assets/crab.png differ
diff --git a/assets/dashboard.png b/assets/dashboard.png
new file mode 100644
index 0000000000..9cd503cd53
Binary files /dev/null and b/assets/dashboard.png differ
diff --git a/assets/demon.png b/assets/demon.png
new file mode 100644
index 0000000000..876599b478
Binary files /dev/null and b/assets/demon.png differ
diff --git a/assets/dm300.png b/assets/dm300.png
new file mode 100644
index 0000000000..6a1d68495a
Binary files /dev/null and b/assets/dm300.png differ
diff --git a/assets/effects.png b/assets/effects.png
new file mode 100644
index 0000000000..2df82bf91c
Binary files /dev/null and b/assets/effects.png differ
diff --git a/assets/elemental.png b/assets/elemental.png
new file mode 100644
index 0000000000..d0090492b6
Binary files /dev/null and b/assets/elemental.png differ
diff --git a/assets/exp_bar.png b/assets/exp_bar.png
new file mode 100644
index 0000000000..81825389f9
Binary files /dev/null and b/assets/exp_bar.png differ
diff --git a/assets/eye.png b/assets/eye.png
new file mode 100644
index 0000000000..5c2487350f
Binary files /dev/null and b/assets/eye.png differ
diff --git a/assets/fireball.png b/assets/fireball.png
new file mode 100644
index 0000000000..0ab9b1a3af
Binary files /dev/null and b/assets/fireball.png differ
diff --git a/assets/font15x.png b/assets/font15x.png
new file mode 100644
index 0000000000..2e0d7320be
Binary files /dev/null and b/assets/font15x.png differ
diff --git a/assets/font1x.png b/assets/font1x.png
new file mode 100644
index 0000000000..7106aafc13
Binary files /dev/null and b/assets/font1x.png differ
diff --git a/assets/font25x.png b/assets/font25x.png
new file mode 100644
index 0000000000..d39d4cf60c
Binary files /dev/null and b/assets/font25x.png differ
diff --git a/assets/font2x.png b/assets/font2x.png
new file mode 100644
index 0000000000..adc6f614b3
Binary files /dev/null and b/assets/font2x.png differ
diff --git a/assets/font3x.png b/assets/font3x.png
new file mode 100644
index 0000000000..87011cdda8
Binary files /dev/null and b/assets/font3x.png differ
diff --git a/assets/game.mp3 b/assets/game.mp3
new file mode 100644
index 0000000000..5306460fbd
Binary files /dev/null and b/assets/game.mp3 differ
diff --git a/assets/ghost.png b/assets/ghost.png
new file mode 100644
index 0000000000..a9ffadbcde
Binary files /dev/null and b/assets/ghost.png differ
diff --git a/assets/gnoll.png b/assets/gnoll.png
new file mode 100644
index 0000000000..a4c614f1b2
Binary files /dev/null and b/assets/gnoll.png differ
diff --git a/assets/golem.png b/assets/golem.png
new file mode 100644
index 0000000000..e9e25d0fe3
Binary files /dev/null and b/assets/golem.png differ
diff --git a/assets/goo.png b/assets/goo.png
new file mode 100644
index 0000000000..5e07a35a85
Binary files /dev/null and b/assets/goo.png differ
diff --git a/assets/hp_bar.png b/assets/hp_bar.png
new file mode 100644
index 0000000000..fa3197e073
Binary files /dev/null and b/assets/hp_bar.png differ
diff --git a/assets/icons.png b/assets/icons.png
new file mode 100644
index 0000000000..608d735c79
Binary files /dev/null and b/assets/icons.png differ
diff --git a/assets/items.png b/assets/items.png
new file mode 100644
index 0000000000..c758c8c5c5
Binary files /dev/null and b/assets/items.png differ
diff --git a/assets/king.png b/assets/king.png
new file mode 100644
index 0000000000..12c58e7b6d
Binary files /dev/null and b/assets/king.png differ
diff --git a/assets/large_buffs.png b/assets/large_buffs.png
new file mode 100644
index 0000000000..42cd34ff87
Binary files /dev/null and b/assets/large_buffs.png differ
diff --git a/assets/larva.png b/assets/larva.png
new file mode 100644
index 0000000000..0a73364eb7
Binary files /dev/null and b/assets/larva.png differ
diff --git a/assets/mage.png b/assets/mage.png
new file mode 100644
index 0000000000..74a41b9abf
Binary files /dev/null and b/assets/mage.png differ
diff --git a/assets/monk.png b/assets/monk.png
new file mode 100644
index 0000000000..8cf78e4ee1
Binary files /dev/null and b/assets/monk.png differ
diff --git a/assets/piranha.png b/assets/piranha.png
new file mode 100644
index 0000000000..3865e624ed
Binary files /dev/null and b/assets/piranha.png differ
diff --git a/assets/plants.png b/assets/plants.png
new file mode 100644
index 0000000000..72604346b0
Binary files /dev/null and b/assets/plants.png differ
diff --git a/assets/ranger.png b/assets/ranger.png
new file mode 100644
index 0000000000..56175742b1
Binary files /dev/null and b/assets/ranger.png differ
diff --git a/assets/rat.png b/assets/rat.png
new file mode 100644
index 0000000000..4b8372fd47
Binary files /dev/null and b/assets/rat.png differ
diff --git a/assets/ratking.png b/assets/ratking.png
new file mode 100644
index 0000000000..d39b944534
Binary files /dev/null and b/assets/ratking.png differ
diff --git a/assets/rogue.png b/assets/rogue.png
new file mode 100644
index 0000000000..42eafb07bf
Binary files /dev/null and b/assets/rogue.png differ
diff --git a/assets/rotting_fist.png b/assets/rotting_fist.png
new file mode 100644
index 0000000000..146a57a943
Binary files /dev/null and b/assets/rotting_fist.png differ
diff --git a/assets/scorpio.png b/assets/scorpio.png
new file mode 100644
index 0000000000..dc1184e515
Binary files /dev/null and b/assets/scorpio.png differ
diff --git a/assets/shaman.png b/assets/shaman.png
new file mode 100644
index 0000000000..fbc101ed08
Binary files /dev/null and b/assets/shaman.png differ
diff --git a/assets/sheep.png b/assets/sheep.png
new file mode 100644
index 0000000000..ab9f37be9b
Binary files /dev/null and b/assets/sheep.png differ
diff --git a/assets/shopkeeper.png b/assets/shopkeeper.png
new file mode 100644
index 0000000000..6b42e91adb
Binary files /dev/null and b/assets/shopkeeper.png differ
diff --git a/assets/skeleton.png b/assets/skeleton.png
new file mode 100644
index 0000000000..8c70a2094f
Binary files /dev/null and b/assets/skeleton.png differ
diff --git a/assets/snd_alert.mp3 b/assets/snd_alert.mp3
new file mode 100644
index 0000000000..c6af7803e1
Binary files /dev/null and b/assets/snd_alert.mp3 differ
diff --git a/assets/snd_badge.mp3 b/assets/snd_badge.mp3
new file mode 100644
index 0000000000..c022ccd857
Binary files /dev/null and b/assets/snd_badge.mp3 differ
diff --git a/assets/snd_beacon.mp3 b/assets/snd_beacon.mp3
new file mode 100644
index 0000000000..9578863561
Binary files /dev/null and b/assets/snd_beacon.mp3 differ
diff --git a/assets/snd_blast.mp3 b/assets/snd_blast.mp3
new file mode 100644
index 0000000000..bd8a2ff742
Binary files /dev/null and b/assets/snd_blast.mp3 differ
diff --git a/assets/snd_bones.mp3 b/assets/snd_bones.mp3
new file mode 100644
index 0000000000..7ec6f0a373
Binary files /dev/null and b/assets/snd_bones.mp3 differ
diff --git a/assets/snd_boss.mp3 b/assets/snd_boss.mp3
new file mode 100644
index 0000000000..942bf3deaf
Binary files /dev/null and b/assets/snd_boss.mp3 differ
diff --git a/assets/snd_burning.mp3 b/assets/snd_burning.mp3
new file mode 100644
index 0000000000..c45ea9f397
Binary files /dev/null and b/assets/snd_burning.mp3 differ
diff --git a/assets/snd_challenge.mp3 b/assets/snd_challenge.mp3
new file mode 100644
index 0000000000..f014020fce
Binary files /dev/null and b/assets/snd_challenge.mp3 differ
diff --git a/assets/snd_charms.mp3 b/assets/snd_charms.mp3
new file mode 100644
index 0000000000..3212caf6ce
Binary files /dev/null and b/assets/snd_charms.mp3 differ
diff --git a/assets/snd_click.mp3 b/assets/snd_click.mp3
new file mode 100644
index 0000000000..5c0728e5e6
Binary files /dev/null and b/assets/snd_click.mp3 differ
diff --git a/assets/snd_cursed.mp3 b/assets/snd_cursed.mp3
new file mode 100644
index 0000000000..74351909cb
Binary files /dev/null and b/assets/snd_cursed.mp3 differ
diff --git a/assets/snd_death.mp3 b/assets/snd_death.mp3
new file mode 100644
index 0000000000..9cae7c85ff
Binary files /dev/null and b/assets/snd_death.mp3 differ
diff --git a/assets/snd_descend.mp3 b/assets/snd_descend.mp3
new file mode 100644
index 0000000000..9ab56bccf1
Binary files /dev/null and b/assets/snd_descend.mp3 differ
diff --git a/assets/snd_dewdrop.mp3 b/assets/snd_dewdrop.mp3
new file mode 100644
index 0000000000..23da344663
Binary files /dev/null and b/assets/snd_dewdrop.mp3 differ
diff --git a/assets/snd_door_open.mp3 b/assets/snd_door_open.mp3
new file mode 100644
index 0000000000..c1b5ba8701
Binary files /dev/null and b/assets/snd_door_open.mp3 differ
diff --git a/assets/snd_drink.mp3 b/assets/snd_drink.mp3
new file mode 100644
index 0000000000..5b86710e7c
Binary files /dev/null and b/assets/snd_drink.mp3 differ
diff --git a/assets/snd_eat.mp3 b/assets/snd_eat.mp3
new file mode 100644
index 0000000000..9a02829ed7
Binary files /dev/null and b/assets/snd_eat.mp3 differ
diff --git a/assets/snd_evoke.mp3 b/assets/snd_evoke.mp3
new file mode 100644
index 0000000000..f3592a97cc
Binary files /dev/null and b/assets/snd_evoke.mp3 differ
diff --git a/assets/snd_falling.mp3 b/assets/snd_falling.mp3
new file mode 100644
index 0000000000..51a1898170
Binary files /dev/null and b/assets/snd_falling.mp3 differ
diff --git a/assets/snd_ghost.mp3 b/assets/snd_ghost.mp3
new file mode 100644
index 0000000000..dc1e5560bf
Binary files /dev/null and b/assets/snd_ghost.mp3 differ
diff --git a/assets/snd_gold.mp3 b/assets/snd_gold.mp3
new file mode 100644
index 0000000000..f3b134be46
Binary files /dev/null and b/assets/snd_gold.mp3 differ
diff --git a/assets/snd_hit.mp3 b/assets/snd_hit.mp3
new file mode 100644
index 0000000000..d122b684b6
Binary files /dev/null and b/assets/snd_hit.mp3 differ
diff --git a/assets/snd_item.mp3 b/assets/snd_item.mp3
new file mode 100644
index 0000000000..dc67e92a6f
Binary files /dev/null and b/assets/snd_item.mp3 differ
diff --git a/assets/snd_levelup.mp3 b/assets/snd_levelup.mp3
new file mode 100644
index 0000000000..9a995edcce
Binary files /dev/null and b/assets/snd_levelup.mp3 differ
diff --git a/assets/snd_lightning.mp3 b/assets/snd_lightning.mp3
new file mode 100644
index 0000000000..e072112f80
Binary files /dev/null and b/assets/snd_lightning.mp3 differ
diff --git a/assets/snd_lullaby.mp3 b/assets/snd_lullaby.mp3
new file mode 100644
index 0000000000..a686a37d58
Binary files /dev/null and b/assets/snd_lullaby.mp3 differ
diff --git a/assets/snd_mastery.mp3 b/assets/snd_mastery.mp3
new file mode 100644
index 0000000000..08e0727735
Binary files /dev/null and b/assets/snd_mastery.mp3 differ
diff --git a/assets/snd_meld.mp3 b/assets/snd_meld.mp3
new file mode 100644
index 0000000000..723130feb7
Binary files /dev/null and b/assets/snd_meld.mp3 differ
diff --git a/assets/snd_miss.mp3 b/assets/snd_miss.mp3
new file mode 100644
index 0000000000..58de0fdf54
Binary files /dev/null and b/assets/snd_miss.mp3 differ
diff --git a/assets/snd_plant.mp3 b/assets/snd_plant.mp3
new file mode 100644
index 0000000000..d4b7e55a53
Binary files /dev/null and b/assets/snd_plant.mp3 differ
diff --git a/assets/snd_puff.mp3 b/assets/snd_puff.mp3
new file mode 100644
index 0000000000..eb4274edeb
Binary files /dev/null and b/assets/snd_puff.mp3 differ
diff --git a/assets/snd_ray.mp3 b/assets/snd_ray.mp3
new file mode 100644
index 0000000000..ef5a21775b
Binary files /dev/null and b/assets/snd_ray.mp3 differ
diff --git a/assets/snd_read.mp3 b/assets/snd_read.mp3
new file mode 100644
index 0000000000..dd1ce822ee
Binary files /dev/null and b/assets/snd_read.mp3 differ
diff --git a/assets/snd_rocks.mp3 b/assets/snd_rocks.mp3
new file mode 100644
index 0000000000..1f94f57cef
Binary files /dev/null and b/assets/snd_rocks.mp3 differ
diff --git a/assets/snd_secret.mp3 b/assets/snd_secret.mp3
new file mode 100644
index 0000000000..934f9e60c4
Binary files /dev/null and b/assets/snd_secret.mp3 differ
diff --git a/assets/snd_shatter.mp3 b/assets/snd_shatter.mp3
new file mode 100644
index 0000000000..33b500ff1c
Binary files /dev/null and b/assets/snd_shatter.mp3 differ
diff --git a/assets/snd_step.mp3 b/assets/snd_step.mp3
new file mode 100644
index 0000000000..fac28ea08e
Binary files /dev/null and b/assets/snd_step.mp3 differ
diff --git a/assets/snd_teleport.mp3 b/assets/snd_teleport.mp3
new file mode 100644
index 0000000000..cc42df593b
Binary files /dev/null and b/assets/snd_teleport.mp3 differ
diff --git a/assets/snd_tomb.mp3 b/assets/snd_tomb.mp3
new file mode 100644
index 0000000000..9a25e13bfd
Binary files /dev/null and b/assets/snd_tomb.mp3 differ
diff --git a/assets/snd_trap.mp3 b/assets/snd_trap.mp3
new file mode 100644
index 0000000000..f09f612f03
Binary files /dev/null and b/assets/snd_trap.mp3 differ
diff --git a/assets/snd_unlock.mp3 b/assets/snd_unlock.mp3
new file mode 100644
index 0000000000..8adf540b90
Binary files /dev/null and b/assets/snd_unlock.mp3 differ
diff --git a/assets/snd_water.mp3 b/assets/snd_water.mp3
new file mode 100644
index 0000000000..1e2310af85
Binary files /dev/null and b/assets/snd_water.mp3 differ
diff --git a/assets/snd_zap.mp3 b/assets/snd_zap.mp3
new file mode 100644
index 0000000000..3c4e3dd49a
Binary files /dev/null and b/assets/snd_zap.mp3 differ
diff --git a/assets/specks.png b/assets/specks.png
new file mode 100644
index 0000000000..1363636d99
Binary files /dev/null and b/assets/specks.png differ
diff --git a/assets/spell_icons.png b/assets/spell_icons.png
new file mode 100644
index 0000000000..81ffebbfb5
Binary files /dev/null and b/assets/spell_icons.png differ
diff --git a/assets/spinner.png b/assets/spinner.png
new file mode 100644
index 0000000000..2605e497fb
Binary files /dev/null and b/assets/spinner.png differ
diff --git a/assets/statue.png b/assets/statue.png
new file mode 100644
index 0000000000..b8ad49022f
Binary files /dev/null and b/assets/statue.png differ
diff --git a/assets/status_pane.png b/assets/status_pane.png
new file mode 100644
index 0000000000..8751f12d4b
Binary files /dev/null and b/assets/status_pane.png differ
diff --git a/assets/succubus.png b/assets/succubus.png
new file mode 100644
index 0000000000..fc577df8cf
Binary files /dev/null and b/assets/succubus.png differ
diff --git a/assets/surface.mp3 b/assets/surface.mp3
new file mode 100644
index 0000000000..d172a29451
Binary files /dev/null and b/assets/surface.mp3 differ
diff --git a/assets/surface.png b/assets/surface.png
new file mode 100644
index 0000000000..75f9a644a9
Binary files /dev/null and b/assets/surface.png differ
diff --git a/assets/swarm.png b/assets/swarm.png
new file mode 100644
index 0000000000..c626735758
Binary files /dev/null and b/assets/swarm.png differ
diff --git a/assets/tengu.png b/assets/tengu.png
new file mode 100644
index 0000000000..407ae307e3
Binary files /dev/null and b/assets/tengu.png differ
diff --git a/assets/theme.mp3 b/assets/theme.mp3
new file mode 100644
index 0000000000..acb7c7a62e
Binary files /dev/null and b/assets/theme.mp3 differ
diff --git a/assets/thief.png b/assets/thief.png
new file mode 100644
index 0000000000..ecdb0b3c7b
Binary files /dev/null and b/assets/thief.png differ
diff --git a/assets/tiles0.png b/assets/tiles0.png
new file mode 100644
index 0000000000..cc42fdfddc
Binary files /dev/null and b/assets/tiles0.png differ
diff --git a/assets/tiles1.png b/assets/tiles1.png
new file mode 100644
index 0000000000..ecf5af50b5
Binary files /dev/null and b/assets/tiles1.png differ
diff --git a/assets/tiles2.png b/assets/tiles2.png
new file mode 100644
index 0000000000..aacab7c17d
Binary files /dev/null and b/assets/tiles2.png differ
diff --git a/assets/tiles3.png b/assets/tiles3.png
new file mode 100644
index 0000000000..8fa5091ca7
Binary files /dev/null and b/assets/tiles3.png differ
diff --git a/assets/tiles4.png b/assets/tiles4.png
new file mode 100644
index 0000000000..fc6e63fc82
Binary files /dev/null and b/assets/tiles4.png differ
diff --git a/assets/toolbar.png b/assets/toolbar.png
new file mode 100644
index 0000000000..89fe80800d
Binary files /dev/null and b/assets/toolbar.png differ
diff --git a/assets/undead.png b/assets/undead.png
new file mode 100644
index 0000000000..ac066eeea2
Binary files /dev/null and b/assets/undead.png differ
diff --git a/assets/wandmaker.png b/assets/wandmaker.png
new file mode 100644
index 0000000000..19e8fe3d67
Binary files /dev/null and b/assets/wandmaker.png differ
diff --git a/assets/warlock.png b/assets/warlock.png
new file mode 100644
index 0000000000..76b5874ce0
Binary files /dev/null and b/assets/warlock.png differ
diff --git a/assets/warrior.png b/assets/warrior.png
new file mode 100644
index 0000000000..a98ee5d3d9
Binary files /dev/null and b/assets/warrior.png differ
diff --git a/assets/water0.png b/assets/water0.png
new file mode 100644
index 0000000000..2f421efba8
Binary files /dev/null and b/assets/water0.png differ
diff --git a/assets/water1.png b/assets/water1.png
new file mode 100644
index 0000000000..95d61515ee
Binary files /dev/null and b/assets/water1.png differ
diff --git a/assets/water2.png b/assets/water2.png
new file mode 100644
index 0000000000..ddef41dea2
Binary files /dev/null and b/assets/water2.png differ
diff --git a/assets/water3.png b/assets/water3.png
new file mode 100644
index 0000000000..e1646eba58
Binary files /dev/null and b/assets/water3.png differ
diff --git a/assets/water4.png b/assets/water4.png
new file mode 100644
index 0000000000..40b76ca495
Binary files /dev/null and b/assets/water4.png differ
diff --git a/assets/wraith.png b/assets/wraith.png
new file mode 100644
index 0000000000..d19a586253
Binary files /dev/null and b/assets/wraith.png differ
diff --git a/assets/yog.png b/assets/yog.png
new file mode 100644
index 0000000000..6665bea07a
Binary files /dev/null and b/assets/yog.png differ
diff --git a/proguard-project.txt b/proguard-project.txt
new file mode 100644
index 0000000000..f2fe1559a2
--- /dev/null
+++ b/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..6d3d04f230
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..747b8f4184
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..ecd7e58330
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..cb74bf9617
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/res/drawable-xxxhdpi/ic_launcher.png b/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..057fc672cc
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 0000000000..3c02242ad0
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
new file mode 100644
index 0000000000..a91fd0372b
--- /dev/null
+++ b/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000000..632a9f6ee9
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ Pixel Dungeon
+
+
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000000..6ce89c7ba4
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/com/watabou/pixeldungeon/Assets.java b/src/com/watabou/pixeldungeon/Assets.java
new file mode 100644
index 0000000000..a940c77265
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Assets.java
@@ -0,0 +1,159 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+public class Assets {
+
+ public static final String ARCS_BG = "arcs1.png";
+ public static final String ARCS_FG = "arcs2.png";
+ public static final String DASHBOARD = "dashboard.png";
+
+ public static final String BANNERS = "banners.png";
+ public static final String BADGES = "badges.png";
+ public static final String AMULET = "amulet.png";
+
+ public static final String CHROME = "chrome.png";
+ public static final String ICONS = "icons.png";
+ public static final String STATUS = "status_pane.png";
+ public static final String HP_BAR = "hp_bar.png";
+ public static final String XP_BAR = "exp_bar.png";
+ public static final String TOOLBAR = "toolbar.png";
+
+ public static final String WARRIOR = "warrior.png";
+ public static final String MAGE = "mage.png";
+ public static final String ROGUE = "rogue.png";
+ public static final String HUNTRESS = "ranger.png";
+ public static final String AVATARS = "avatars.png";
+
+ public static final String SURFACE = "surface.png";
+
+ public static final String FIREBALL = "fireball.png";
+ public static final String SPECKS = "specks.png";
+ public static final String EFFECTS = "effects.png";
+
+ public static final String RAT = "rat.png";
+ public static final String GNOLL = "gnoll.png";
+ public static final String CRAB = "crab.png";
+ public static final String GOO = "goo.png";
+ public static final String SWARM = "swarm.png";
+ public static final String SKELETON = "skeleton.png";
+ public static final String SHAMAN = "shaman.png";
+ public static final String THIEF = "thief.png";
+ public static final String TENGU = "tengu.png";
+ public static final String SHEEP = "sheep.png";
+ public static final String KEEPER = "shopkeeper.png";
+ public static final String BAT = "bat.png";
+ public static final String BRUTE = "brute.png";
+ public static final String SPINNER = "spinner.png";
+ public static final String DM300 = "dm300.png";
+ public static final String WRAITH = "wraith.png";
+ public static final String ELEMENTAL= "elemental.png";
+ public static final String MONK = "monk.png";
+ public static final String WARLOCK = "warlock.png";
+ public static final String GOLEM = "golem.png";
+ public static final String UNDEAD = "undead.png";
+ public static final String KING = "king.png";
+ public static final String STATUE = "statue.png";
+ public static final String PIRANHA = "piranha.png";
+ public static final String EYE = "eye.png";
+ public static final String SUCCUBUS = "succubus.png";
+ public static final String SCORPIO = "scorpio.png";
+ public static final String ROTTING = "rotting_fist.png";
+ public static final String BURNING = "burning_fist.png";
+ public static final String YOG = "yog.png";
+ public static final String LARVA = "larva.png";
+ public static final String GHOST = "ghost.png";
+ public static final String MAKER = "wandmaker.png";
+ public static final String TROLL = "blacksmith.png";
+ public static final String IMP = "demon.png";
+ public static final String RATKING = "ratking.png";
+
+ public static final String ITEMS = "items.png";
+ public static final String PLANTS = "plants.png";
+
+ public static final String TILES_SEWERS = "tiles0.png";
+ public static final String TILES_PRISON = "tiles1.png";
+ public static final String TILES_CAVES = "tiles2.png";
+ public static final String TILES_CITY = "tiles3.png";
+ public static final String TILES_HALLS = "tiles4.png";
+
+ public static final String WATER_SEWERS = "water0.png";
+ public static final String WATER_PRISON = "water1.png";
+ public static final String WATER_CAVES = "water2.png";
+ public static final String WATER_CITY = "water3.png";
+ public static final String WATER_HALLS = "water4.png";
+
+ public static final String BUFFS_SMALL = "buffs.png";
+ public static final String BUFFS_LARGE = "large_buffs.png";
+ public static final String SPELL_ICONS = "spell_icons.png";
+
+ public static final String FONTS1X = "font1x.png";
+ public static final String FONTS15X = "font15x.png";
+ public static final String FONTS2X = "font2x.png";
+ public static final String FONTS25X = "font25x.png";
+ public static final String FONTS3X = "font3x.png";
+
+ public static final String THEME = "theme.mp3";
+ public static final String TUNE = "game.mp3";
+ public static final String HAPPY = "surface.mp3";
+
+ public static final String SND_CLICK = "snd_click.mp3";
+ public static final String SND_BADGE = "snd_badge.mp3";
+ public static final String SND_GOLD = "snd_gold.mp3";
+
+ public static final String SND_OPEN = "snd_door_open.mp3";
+ public static final String SND_UNLOCK = "snd_unlock.mp3";
+ public static final String SND_ITEM = "snd_item.mp3";
+ public static final String SND_DEWDROP = "snd_dewdrop.mp3";
+ public static final String SND_HIT = "snd_hit.mp3";
+ public static final String SND_MISS = "snd_miss.mp3";
+ public static final String SND_STEP = "snd_step.mp3";
+ public static final String SND_WATER = "snd_water.mp3";
+ public static final String SND_DESCEND = "snd_descend.mp3";
+ public static final String SND_EAT = "snd_eat.mp3";
+ public static final String SND_READ = "snd_read.mp3";
+ public static final String SND_LULLABY = "snd_lullaby.mp3";
+ public static final String SND_DRINK = "snd_drink.mp3";
+ public static final String SND_SHATTER = "snd_shatter.mp3";
+ public static final String SND_ZAP = "snd_zap.mp3";
+ public static final String SND_LIGHTNING= "snd_lightning.mp3";
+ public static final String SND_LEVELUP = "snd_levelup.mp3";
+ public static final String SND_DEATH = "snd_death.mp3";
+ public static final String SND_CHALLENGE= "snd_challenge.mp3";
+ public static final String SND_CURSED = "snd_cursed.mp3";
+ public static final String SND_TRAP = "snd_trap.mp3";
+ public static final String SND_EVOKE = "snd_evoke.mp3";
+ public static final String SND_TOMB = "snd_tomb.mp3";
+ public static final String SND_ALERT = "snd_alert.mp3";
+ public static final String SND_MELD = "snd_meld.mp3";
+ public static final String SND_BOSS = "snd_boss.mp3";
+ public static final String SND_BLAST = "snd_blast.mp3";
+ public static final String SND_PLANT = "snd_plant.mp3";
+ public static final String SND_RAY = "snd_ray.mp3";
+ public static final String SND_BEACON = "snd_beacon.mp3";
+ public static final String SND_TELEPORT = "snd_teleport.mp3";
+ public static final String SND_CHARMS = "snd_charms.mp3";
+ public static final String SND_MASTERY = "snd_mastery.mp3";
+ public static final String SND_PUFF = "snd_puff.mp3";
+ public static final String SND_ROCKS = "snd_rocks.mp3";
+ public static final String SND_BURNING = "snd_burning.mp3";
+ public static final String SND_FALLING = "snd_falling.mp3";
+ public static final String SND_GHOST = "snd_ghost.mp3";
+ public static final String SND_SECRET = "snd_secret.mp3";
+ public static final String SND_BONES = "snd_bones.mp3";
+}
diff --git a/src/com/watabou/pixeldungeon/Badges.java b/src/com/watabou/pixeldungeon/Badges.java
new file mode 100644
index 0000000000..1cb3ad9efb
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Badges.java
@@ -0,0 +1,927 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.actors.mobs.Acidic;
+import com.watabou.pixeldungeon.actors.mobs.Albino;
+import com.watabou.pixeldungeon.actors.mobs.Bandit;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.actors.mobs.Senior;
+import com.watabou.pixeldungeon.actors.mobs.Shielded;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.bags.ScrollHolder;
+import com.watabou.pixeldungeon.items.bags.SeedPouch;
+import com.watabou.pixeldungeon.items.bags.WandHolster;
+import com.watabou.pixeldungeon.items.potions.Potion;
+import com.watabou.pixeldungeon.items.rings.Ring;
+import com.watabou.pixeldungeon.items.rings.RingOfHaggler;
+import com.watabou.pixeldungeon.items.rings.RingOfThorns;
+import com.watabou.pixeldungeon.items.scrolls.Scroll;
+import com.watabou.pixeldungeon.items.wands.Wand;
+import com.watabou.pixeldungeon.scenes.PixelScene;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Bundle;
+
+public class Badges {
+
+ public static enum Badge {
+ MONSTERS_SLAIN_1( "10 enemies slain", 0 ),
+ MONSTERS_SLAIN_2( "50 enemies slain", 1 ),
+ MONSTERS_SLAIN_3( "150 enemies slain", 2 ),
+ MONSTERS_SLAIN_4( "250 enemies slain", 3 ),
+ GOLD_COLLECTED_1( "100 gold collected", 4 ),
+ GOLD_COLLECTED_2( "500 gold collected", 5 ),
+ GOLD_COLLECTED_3( "2500 gold collected", 6 ),
+ GOLD_COLLECTED_4( "7500 gold collected", 7 ),
+ LEVEL_REACHED_1( "Level 6 reached", 8 ),
+ LEVEL_REACHED_2( "Level 12 reached", 9 ),
+ LEVEL_REACHED_3( "Level 18 reached", 10 ),
+ LEVEL_REACHED_4( "Level 24 reached", 11 ),
+ ALL_POTIONS_IDENTIFIED( "All potions identified", 16 ),
+ ALL_SCROLLS_IDENTIFIED( "All scrolls identified", 17 ),
+ ALL_RINGS_IDENTIFIED( "All rings identified", 18 ),
+ ALL_WANDS_IDENTIFIED( "All wands identified", 19 ),
+ ALL_ITEMS_IDENTIFIED( "All potions, scrolls, rings & wands identified", 35, true ),
+ BAG_BOUGHT_SEED_POUCH,
+ BAG_BOUGHT_SCROLL_HOLDER,
+ BAG_BOUGHT_WAND_HOLSTER,
+ ALL_BAGS_BOUGHT( "All bags bought", 23 ),
+ DEATH_FROM_FIRE( "Death from fire", 24 ),
+ DEATH_FROM_POISON( "Death from poison", 25 ),
+ DEATH_FROM_GAS( "Death from toxic gas", 26 ),
+ DEATH_FROM_HUNGER( "Death from hunger", 27 ),
+ DEATH_FROM_GLYPH( "Death from a glyph", 57 ),
+ DEATH_FROM_FALLING( "Death from falling down", 59 ),
+ YASD( "Death from fire, poison, toxic gas & hunger", 34, true ),
+ BOSS_SLAIN_1_WARRIOR,
+ BOSS_SLAIN_1_MAGE,
+ BOSS_SLAIN_1_ROGUE,
+ BOSS_SLAIN_1_HUNTRESS,
+ BOSS_SLAIN_1( "1st boss slain", 12 ),
+ BOSS_SLAIN_2( "2nd boss slain", 13 ),
+ BOSS_SLAIN_3( "3rd boss slain", 14 ),
+ BOSS_SLAIN_4( "4th boss slain", 15 ),
+ BOSS_SLAIN_1_ALL_CLASSES( "1st boss slain by Warrior, Mage, Rogue & Huntress", 32, true ),
+ BOSS_SLAIN_3_GLADIATOR,
+ BOSS_SLAIN_3_BERSERKER,
+ BOSS_SLAIN_3_WARLOCK,
+ BOSS_SLAIN_3_BATTLEMAGE,
+ BOSS_SLAIN_3_FREERUNNER,
+ BOSS_SLAIN_3_ASSASSIN,
+ BOSS_SLAIN_3_SNIPER,
+ BOSS_SLAIN_3_WARDEN,
+ BOSS_SLAIN_3_ALL_SUBCLASSES(
+ "3rd boss slain by Gladiator, Berserker, Warlock, Battlemage, " +
+ "Freerunner, Assassin, Sniper & Warden", 33, true ),
+ RING_OF_HAGGLER( "Ring of Haggler obtained", 20 ),
+ RING_OF_THORNS( "Ring of Thorns obtained", 21 ),
+ STRENGTH_ATTAINED_1( "13 points of Strength attained", 40 ),
+ STRENGTH_ATTAINED_2( "15 points of Strength attained", 41 ),
+ STRENGTH_ATTAINED_3( "17 points of Strength attained", 42 ),
+ STRENGTH_ATTAINED_4( "19 points of Strength attained", 43 ),
+ FOOD_EATEN_1( "10 pieces of food eaten", 44 ),
+ FOOD_EATEN_2( "20 pieces of food eaten", 45 ),
+ FOOD_EATEN_3( "30 pieces of food eaten", 46 ),
+ FOOD_EATEN_4( "40 pieces of food eaten", 47 ),
+ MASTERY_WARRIOR,
+ MASTERY_MAGE,
+ MASTERY_ROGUE,
+ MASTERY_HUNTRESS,
+ ITEM_LEVEL_1( "Item of level 3 acquired", 48 ),
+ ITEM_LEVEL_2( "Item of level 6 acquired", 49 ),
+ ITEM_LEVEL_3( "Item of level 9 acquired", 50 ),
+ ITEM_LEVEL_4( "Item of level 12 acquired", 51 ),
+ RARE_ALBINO,
+ RARE_BANDIT,
+ RARE_SHIELDED,
+ RARE_SENIOR,
+ RARE_ACIDIC,
+ RARE( "All rare monsters slain", 37, true ),
+ VICTORY_WARRIOR,
+ VICTORY_MAGE,
+ VICTORY_ROGUE,
+ VICTORY_HUNTRESS,
+ VICTORY( "Amulet of Yendor obtained", 22 ),
+ VICTORY_ALL_CLASSES( "Amulet of Yendor obtained by Warrior, Mage, Rogue & Huntress", 36, true ),
+ MASTERY_COMBO( "7-hit combo", 56 ),
+ POTIONS_COOKED_1( "3 potions cooked", 52 ),
+ POTIONS_COOKED_2( "6 potions cooked", 53 ),
+ POTIONS_COOKED_3( "9 potions cooked", 54 ),
+ POTIONS_COOKED_4( "12 potions cooked", 55 ),
+ NO_MONSTERS_SLAIN( "Level completed without killing any monsters", 28 ),
+ GRIM_WEAPON( "Monster killed by a Grim weapon", 29 ),
+ PIRANHAS( "6 piranhas killed", 30 ),
+ NIGHT_HUNTER( "15 monsters killed at nighttime", 58 ),
+ GAMES_PLAYED_1( "10 games played", 60, true ),
+ GAMES_PLAYED_2( "100 games played", 61, true ),
+ GAMES_PLAYED_3( "500 games played", 62, true ),
+ GAMES_PLAYED_4( "2000 games played", 63, true ),
+ HAPPY_END( "Happy end", 38 ),
+ SUPPORTER( "Thanks for your support!", 31, true );
+
+ public boolean meta;
+
+ public String description;
+ public int image;
+
+ private Badge( String description, int image ) {
+ this( description, image, false );
+ }
+
+ private Badge( String description, int image, boolean meta ) {
+ this.description = description;
+ this.image = image;
+ this.meta = meta;
+ }
+
+ private Badge() {
+ this( "", -1 );
+ }
+ }
+
+ private static HashSet global;
+ private static HashSet local = new HashSet();
+
+ private static boolean saveNeeded = false;
+
+ public static void reset() {
+ local.clear();
+ loadGlobal();
+ }
+
+ private static final String BADGES_FILE = "badges.dat";
+ private static final String BADGES = "badges";
+
+ private static HashSet restore( Bundle bundle ) {
+ HashSet badges = new HashSet();
+
+ String[] names = bundle.getStringArray( BADGES );
+ for (int i=0; i < names.length; i++) {
+ try {
+ badges.add( Badge.valueOf( names[i] ) );
+ } catch (Exception e) {
+ }
+ }
+
+ return badges;
+ }
+
+ private static void store( Bundle bundle, HashSet badges ) {
+ int count = 0;
+ String names[] = new String[global.size()];
+
+ for (Badge badge:badges) {
+ names[count++] = badge.toString();
+ }
+ bundle.put( BADGES, names );
+ }
+
+ public static void loadLocal( Bundle bundle ) {
+ local = restore( bundle );
+ }
+
+ public static void saveLocal( Bundle bundle ) {
+ store( bundle, local );
+ }
+
+ public static void loadGlobal() {
+ if (global == null) {
+ try {
+ InputStream input = Game.instance.openFileInput( BADGES_FILE );
+ Bundle bundle = Bundle.read( input );
+ input.close();
+
+ global = restore( bundle );
+
+ } catch (IOException e) {
+ global = new HashSet();
+ }
+ }
+ }
+
+ public static void saveGlobal() {
+ if (saveNeeded) {
+
+ Bundle bundle = new Bundle();
+ store( bundle, global );
+
+ try {
+ OutputStream output = Game.instance.openFileOutput( BADGES_FILE, Game.MODE_PRIVATE );
+ Bundle.write( bundle, output );
+ output.close();
+ saveNeeded = false;
+ } catch (IOException e) {
+
+ }
+ }
+ }
+
+ public static void validateMonstersSlain() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.MONSTERS_SLAIN_1 ) && Statistics.enemiesSlain >= 10) {
+ badge = Badge.MONSTERS_SLAIN_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.MONSTERS_SLAIN_2 ) && Statistics.enemiesSlain >= 50) {
+ badge = Badge.MONSTERS_SLAIN_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.MONSTERS_SLAIN_3 ) && Statistics.enemiesSlain >= 150) {
+ badge = Badge.MONSTERS_SLAIN_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.MONSTERS_SLAIN_4 ) && Statistics.enemiesSlain >= 250) {
+ badge = Badge.MONSTERS_SLAIN_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateGoldCollected() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.GOLD_COLLECTED_1 ) && Statistics.goldCollected >= 100) {
+ badge = Badge.GOLD_COLLECTED_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.GOLD_COLLECTED_2 ) && Statistics.goldCollected >= 500) {
+ badge = Badge.GOLD_COLLECTED_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.GOLD_COLLECTED_3 ) && Statistics.goldCollected >= 2500) {
+ badge = Badge.GOLD_COLLECTED_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.GOLD_COLLECTED_4 ) && Statistics.goldCollected >= 7500) {
+ badge = Badge.GOLD_COLLECTED_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateLevelReached() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.LEVEL_REACHED_1 ) && Dungeon.hero.lvl >= 6) {
+ badge = Badge.LEVEL_REACHED_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.LEVEL_REACHED_2 ) && Dungeon.hero.lvl >= 12) {
+ badge = Badge.LEVEL_REACHED_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.LEVEL_REACHED_3 ) && Dungeon.hero.lvl >= 18) {
+ badge = Badge.LEVEL_REACHED_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.LEVEL_REACHED_4 ) && Dungeon.hero.lvl >= 24) {
+ badge = Badge.LEVEL_REACHED_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateStrengthAttained() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.STRENGTH_ATTAINED_1 ) && Dungeon.hero.STR >= 13) {
+ badge = Badge.STRENGTH_ATTAINED_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.STRENGTH_ATTAINED_2 ) && Dungeon.hero.STR >= 15) {
+ badge = Badge.STRENGTH_ATTAINED_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.STRENGTH_ATTAINED_3 ) && Dungeon.hero.STR >= 17) {
+ badge = Badge.STRENGTH_ATTAINED_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.STRENGTH_ATTAINED_4 ) && Dungeon.hero.STR >= 19) {
+ badge = Badge.STRENGTH_ATTAINED_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateFoodEaten() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.FOOD_EATEN_1 ) && Statistics.foodEaten >= 10) {
+ badge = Badge.FOOD_EATEN_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.FOOD_EATEN_2 ) && Statistics.foodEaten >= 20) {
+ badge = Badge.FOOD_EATEN_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.FOOD_EATEN_3 ) && Statistics.foodEaten >= 30) {
+ badge = Badge.FOOD_EATEN_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.FOOD_EATEN_4 ) && Statistics.foodEaten >= 40) {
+ badge = Badge.FOOD_EATEN_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validatePotionsCooked() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.POTIONS_COOKED_1 ) && Statistics.potionsCooked >= 3) {
+ badge = Badge.POTIONS_COOKED_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.POTIONS_COOKED_2 ) && Statistics.potionsCooked >= 6) {
+ badge = Badge.POTIONS_COOKED_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.POTIONS_COOKED_3 ) && Statistics.potionsCooked >= 9) {
+ badge = Badge.POTIONS_COOKED_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.POTIONS_COOKED_4 ) && Statistics.potionsCooked >= 12) {
+ badge = Badge.POTIONS_COOKED_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validatePiranhasKilled() {
+ Badge badge = null;
+
+ if (!local.contains( Badge.PIRANHAS ) && Statistics.piranhasKilled >= 6) {
+ badge = Badge.PIRANHAS;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateItemLevelAquired( Item item ) {
+
+ // This method should be called:
+ // 1) When an item is obtained (Item.collect)
+ // 2) When an item is upgraded (ScrollOfUpgrade, ScrollOfWeaponUpgrade, ShortSword, WandOfMagicMissile)
+ // 3) When an item is identified
+ if (!item.levelKnown) {
+ return;
+ }
+
+ Badge badge = null;
+
+ if (!local.contains( Badge.ITEM_LEVEL_1 ) && item.level >= 3) {
+ badge = Badge.ITEM_LEVEL_1;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.ITEM_LEVEL_2 ) && item.level >= 6) {
+ badge = Badge.ITEM_LEVEL_2;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.ITEM_LEVEL_3 ) && item.level >= 9) {
+ badge = Badge.ITEM_LEVEL_3;
+ local.add( badge );
+ }
+ if (!local.contains( Badge.ITEM_LEVEL_4 ) && item.level >= 12) {
+ badge = Badge.ITEM_LEVEL_4;
+ local.add( badge );
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateAllPotionsIdentified() {
+ if (Dungeon.hero != null && Dungeon.hero.isAlive() &&
+ !local.contains( Badge.ALL_POTIONS_IDENTIFIED ) && Potion.allKnown()) {
+
+ Badge badge = Badge.ALL_POTIONS_IDENTIFIED;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateAllItemsIdentified();
+ }
+ }
+
+ public static void validateAllScrollsIdentified() {
+ if (Dungeon.hero != null && Dungeon.hero.isAlive() &&
+ !local.contains( Badge.ALL_SCROLLS_IDENTIFIED ) && Scroll.allKnown()) {
+
+ Badge badge = Badge.ALL_SCROLLS_IDENTIFIED;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateAllItemsIdentified();
+ }
+ }
+
+ public static void validateAllRingsIdentified() {
+ if (Dungeon.hero != null && Dungeon.hero.isAlive() &&
+ !local.contains( Badge.ALL_RINGS_IDENTIFIED ) && Ring.allKnown()) {
+
+ Badge badge = Badge.ALL_RINGS_IDENTIFIED;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateAllItemsIdentified();
+ }
+ }
+
+ public static void validateAllWandsIdentified() {
+ if (Dungeon.hero != null && Dungeon.hero.isAlive() &&
+ !local.contains( Badge.ALL_WANDS_IDENTIFIED ) && Wand.allKnown()) {
+
+ Badge badge = Badge.ALL_WANDS_IDENTIFIED;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateAllItemsIdentified();
+ }
+ }
+
+ public static void validateAllBagsBought( Item bag ) {
+
+ Badge badge = null;
+ if (bag instanceof SeedPouch) {
+ badge = Badge.BAG_BOUGHT_SEED_POUCH;
+ } else if (bag instanceof ScrollHolder) {
+ badge = Badge.BAG_BOUGHT_SCROLL_HOLDER;
+ } else if (bag instanceof WandHolster) {
+ badge = Badge.BAG_BOUGHT_WAND_HOLSTER;
+ }
+
+ if (badge != null) {
+
+ local.add( badge );
+
+ if (!local.contains( Badge.ALL_BAGS_BOUGHT ) &&
+ local.contains( Badge.BAG_BOUGHT_SCROLL_HOLDER ) &&
+ local.contains( Badge.BAG_BOUGHT_SEED_POUCH ) &&
+ local.contains( Badge.BAG_BOUGHT_WAND_HOLSTER )) {
+
+ badge = Badge.ALL_BAGS_BOUGHT;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+ }
+
+ public static void validateAllItemsIdentified() {
+ if (!global.contains( Badge.ALL_ITEMS_IDENTIFIED ) &&
+ global.contains( Badge.ALL_POTIONS_IDENTIFIED ) &&
+ global.contains( Badge.ALL_SCROLLS_IDENTIFIED ) &&
+ global.contains( Badge.ALL_RINGS_IDENTIFIED ) &&
+ global.contains( Badge.ALL_WANDS_IDENTIFIED )) {
+
+ Badge badge = Badge.ALL_ITEMS_IDENTIFIED;
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateDeathFromFire() {
+ Badge badge = Badge.DEATH_FROM_FIRE;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateYASD();
+ }
+
+ public static void validateDeathFromPoison() {
+ Badge badge = Badge.DEATH_FROM_POISON;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateYASD();
+ }
+
+ public static void validateDeathFromGas() {
+ Badge badge = Badge.DEATH_FROM_GAS;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateYASD();
+ }
+
+ public static void validateDeathFromHunger() {
+ Badge badge = Badge.DEATH_FROM_HUNGER;
+ local.add( badge );
+ displayBadge( badge );
+
+ validateYASD();
+ }
+
+ public static void validateDeathFromGlyph() {
+ Badge badge = Badge.DEATH_FROM_GLYPH;
+ local.add( badge );
+ displayBadge( badge );
+ }
+
+ public static void validateDeathFromFalling() {
+ Badge badge = Badge.DEATH_FROM_FALLING;
+ local.add( badge );
+ displayBadge( badge );
+ }
+
+ private static void validateYASD() {
+ if (global.contains( Badge.DEATH_FROM_FIRE ) &&
+ global.contains( Badge.DEATH_FROM_POISON ) &&
+ global.contains( Badge.DEATH_FROM_GAS ) &&
+ global.contains( Badge.DEATH_FROM_HUNGER)) {
+
+ Badge badge = Badge.YASD;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateBossSlain() {
+ Badge badge = null;
+ switch (Dungeon.depth) {
+ case 5:
+ badge = Badge.BOSS_SLAIN_1;
+ break;
+ case 10:
+ badge = Badge.BOSS_SLAIN_2;
+ break;
+ case 15:
+ badge = Badge.BOSS_SLAIN_3;
+ break;
+ case 20:
+ badge = Badge.BOSS_SLAIN_4;
+ break;
+ }
+
+ if (badge != null) {
+ local.add( badge );
+ displayBadge( badge );
+
+ if (badge == Badge.BOSS_SLAIN_1) {
+ switch (Dungeon.hero.heroClass) {
+ case WARRIOR:
+ badge = Badge.BOSS_SLAIN_1_WARRIOR;
+ break;
+ case MAGE:
+ badge = Badge.BOSS_SLAIN_1_MAGE;
+ break;
+ case ROGUE:
+ badge = Badge.BOSS_SLAIN_1_ROGUE;
+ break;
+ case HUNTRESS:
+ badge = Badge.BOSS_SLAIN_1_HUNTRESS;
+ break;
+ }
+ local.add( badge );
+ if (!global.contains( badge )) {
+ global.add( badge );
+ saveNeeded = true;
+ }
+
+ if (global.contains( Badge.BOSS_SLAIN_1_WARRIOR ) &&
+ global.contains( Badge.BOSS_SLAIN_1_MAGE ) &&
+ global.contains( Badge.BOSS_SLAIN_1_ROGUE ) &&
+ global.contains( Badge.BOSS_SLAIN_1_HUNTRESS)) {
+
+ badge = Badge.BOSS_SLAIN_1_ALL_CLASSES;
+ if (!global.contains( badge )) {
+ displayBadge( badge );
+ global.add( badge );
+ saveNeeded = true;
+ }
+ }
+ } else
+ if (badge == Badge.BOSS_SLAIN_3) {
+ switch (Dungeon.hero.subClass) {
+ case GLADIATOR:
+ badge = Badge.BOSS_SLAIN_3_GLADIATOR;
+ break;
+ case BERSERKER:
+ badge = Badge.BOSS_SLAIN_3_BERSERKER;
+ break;
+ case WARLOCK:
+ badge = Badge.BOSS_SLAIN_3_WARLOCK;
+ break;
+ case BATTLEMAGE:
+ badge = Badge.BOSS_SLAIN_3_BATTLEMAGE;
+ break;
+ case FREERUNNER:
+ badge = Badge.BOSS_SLAIN_3_FREERUNNER;
+ break;
+ case ASSASSIN:
+ badge = Badge.BOSS_SLAIN_3_ASSASSIN;
+ break;
+ case SNIPER:
+ badge = Badge.BOSS_SLAIN_3_SNIPER;
+ break;
+ case WARDEN:
+ badge = Badge.BOSS_SLAIN_3_WARDEN;
+ break;
+ default:
+ return;
+ }
+ local.add( badge );
+ if (!global.contains( badge )) {
+ global.add( badge );
+ saveNeeded = true;
+ }
+
+ if (global.contains( Badge.BOSS_SLAIN_3_GLADIATOR ) &&
+ global.contains( Badge.BOSS_SLAIN_3_BERSERKER ) &&
+ global.contains( Badge.BOSS_SLAIN_3_WARLOCK ) &&
+ global.contains( Badge.BOSS_SLAIN_3_BATTLEMAGE ) &&
+ global.contains( Badge.BOSS_SLAIN_3_FREERUNNER ) &&
+ global.contains( Badge.BOSS_SLAIN_3_ASSASSIN ) &&
+ global.contains( Badge.BOSS_SLAIN_3_SNIPER ) &&
+ global.contains( Badge.BOSS_SLAIN_3_WARDEN )) {
+
+ badge = Badge.BOSS_SLAIN_3_ALL_SUBCLASSES;
+ if (!global.contains( badge )) {
+ displayBadge( badge );
+ global.add( badge );
+ saveNeeded = true;
+ }
+ }
+ }
+ }
+ }
+
+ public static void validateMastery() {
+
+ Badge badge = null;
+ switch (Dungeon.hero.heroClass) {
+ case WARRIOR:
+ badge = Badge.MASTERY_WARRIOR;
+ break;
+ case MAGE:
+ badge = Badge.MASTERY_MAGE;
+ break;
+ case ROGUE:
+ badge = Badge.MASTERY_ROGUE;
+ break;
+ case HUNTRESS:
+ badge = Badge.MASTERY_HUNTRESS;
+ break;
+ }
+
+ if (!global.contains( badge )) {
+ global.add( badge );
+ saveNeeded = true;
+ }
+ }
+
+ public static void validateMasteryCombo( int n ) {
+ if (!local.contains( Badge.MASTERY_COMBO ) && n == 7) {
+ Badge badge = Badge.MASTERY_COMBO;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateRingOfHaggler() {
+ if (!local.contains( Badge.RING_OF_HAGGLER ) && new RingOfHaggler().isKnown()) {
+ Badge badge = Badge.RING_OF_HAGGLER;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateRingOfThorns() {
+ if (!local.contains( Badge.RING_OF_THORNS ) && new RingOfThorns().isKnown()) {
+ Badge badge = Badge.RING_OF_THORNS;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateRare( Mob mob ) {
+
+ Badge badge = null;
+ if (mob instanceof Albino) {
+ badge = Badge.RARE_ALBINO;
+ } else if (mob instanceof Bandit) {
+ badge = Badge.RARE_BANDIT;
+ } else if (mob instanceof Shielded) {
+ badge = Badge.RARE_SHIELDED;
+ } else if (mob instanceof Senior) {
+ badge = Badge.RARE_SENIOR;
+ } else if (mob instanceof Acidic) {
+ badge = Badge.RARE_ACIDIC;
+ }
+ if (!global.contains( badge )) {
+ global.add( badge );
+ saveNeeded = true;
+ }
+
+ if (global.contains( Badge.RARE_ALBINO ) &&
+ global.contains( Badge.RARE_BANDIT ) &&
+ global.contains( Badge.RARE_SHIELDED ) &&
+ global.contains( Badge.RARE_SENIOR ) &&
+ global.contains( Badge.RARE_ACIDIC )) {
+
+ badge = Badge.RARE;
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateVictory() {
+
+ Badge badge = Badge.VICTORY;
+ displayBadge( badge );
+
+ switch (Dungeon.hero.heroClass) {
+ case WARRIOR:
+ badge = Badge.VICTORY_WARRIOR;
+ break;
+ case MAGE:
+ badge = Badge.VICTORY_MAGE;
+ break;
+ case ROGUE:
+ badge = Badge.VICTORY_ROGUE;
+ break;
+ case HUNTRESS:
+ badge = Badge.VICTORY_HUNTRESS;
+ break;
+ }
+ local.add( badge );
+ if (!global.contains( badge )) {
+ global.add( badge );
+ saveNeeded = true;
+ }
+
+ if (global.contains( Badge.VICTORY_WARRIOR ) &&
+ global.contains( Badge.VICTORY_MAGE ) &&
+ global.contains( Badge.VICTORY_ROGUE ) &&
+ global.contains( Badge.VICTORY_HUNTRESS )) {
+
+ badge = Badge.VICTORY_ALL_CLASSES;
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateNoKilling() {
+ if (!local.contains( Badge.NO_MONSTERS_SLAIN ) && Statistics.completedWithNoKilling) {
+ Badge badge = Badge.NO_MONSTERS_SLAIN;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateGrimWeapon() {
+ if (!local.contains( Badge.GRIM_WEAPON )) {
+ Badge badge = Badge.GRIM_WEAPON;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateNightHunter() {
+ if (!local.contains( Badge.NIGHT_HUNTER ) && Statistics.nightHunt >= 15) {
+ Badge badge = Badge.NIGHT_HUNTER;
+ local.add( badge );
+ displayBadge( badge );
+ }
+ }
+
+ public static void validateSupporter() {
+
+ global.add( Badge.SUPPORTER );
+ saveNeeded = true;
+
+ PixelScene.showBadge( Badge.SUPPORTER );
+ }
+
+ public static void validateGamesPlayed() {
+ Badge badge = null;
+ if (Rankings.INSTANCE.totalNumber >= 10) {
+ badge = Badge.GAMES_PLAYED_1;
+ }
+ if (Rankings.INSTANCE.totalNumber >= 100) {
+ badge = Badge.GAMES_PLAYED_2;
+ }
+ if (Rankings.INSTANCE.totalNumber >= 500) {
+ badge = Badge.GAMES_PLAYED_3;
+ }
+ if (Rankings.INSTANCE.totalNumber >= 2000) {
+ badge = Badge.GAMES_PLAYED_4;
+ }
+
+ displayBadge( badge );
+ }
+
+ public static void validateHappyEnd() {
+ displayBadge( Badge.HAPPY_END );
+ }
+
+ private static void displayBadge( Badge badge ) {
+
+ if (badge == null) {
+ return;
+ }
+
+ if (global.contains( badge )) {
+
+ if (!badge.meta) {
+ GLog.h( "Badge endorsed: %s", badge.description );
+ }
+
+ } else {
+
+ global.add( badge );
+ saveNeeded = true;
+
+ if (badge.meta) {
+ GLog.h( "New super badge: %s", badge.description );
+ } else {
+ GLog.h( "New badge: %s", badge.description );
+ }
+ PixelScene.showBadge( badge );
+ }
+ }
+
+ public static boolean isUnlocked( Badge badge ) {
+ return global.contains( badge );
+ }
+
+ public static void disown( Badge badge ) {
+ loadGlobal();
+ global.remove( badge );
+ saveNeeded = true;
+ }
+
+ public static List filtered( boolean global ) {
+
+ HashSet filtered = new HashSet( global ? Badges.global : Badges.local );
+
+ if (!global) {
+ Iterator iterator = filtered.iterator();
+ while (iterator.hasNext()) {
+ Badge badge = iterator.next();
+ if (badge.meta) {
+ iterator.remove();
+ }
+ }
+ }
+
+ leaveBest( filtered, Badge.MONSTERS_SLAIN_1, Badge.MONSTERS_SLAIN_2, Badge.MONSTERS_SLAIN_3, Badge.MONSTERS_SLAIN_4 );
+ leaveBest( filtered, Badge.GOLD_COLLECTED_1, Badge.GOLD_COLLECTED_2, Badge.GOLD_COLLECTED_3, Badge.GOLD_COLLECTED_4 );
+ leaveBest( filtered, Badge.BOSS_SLAIN_1, Badge.BOSS_SLAIN_2, Badge.BOSS_SLAIN_3, Badge.BOSS_SLAIN_4 );
+ leaveBest( filtered, Badge.LEVEL_REACHED_1, Badge.LEVEL_REACHED_2, Badge.LEVEL_REACHED_3, Badge.LEVEL_REACHED_4 );
+ leaveBest( filtered, Badge.STRENGTH_ATTAINED_1, Badge.STRENGTH_ATTAINED_2, Badge.STRENGTH_ATTAINED_3, Badge.STRENGTH_ATTAINED_4 );
+ leaveBest( filtered, Badge.FOOD_EATEN_1, Badge.FOOD_EATEN_2, Badge.FOOD_EATEN_3, Badge.FOOD_EATEN_4 );
+ leaveBest( filtered, Badge.ITEM_LEVEL_1, Badge.ITEM_LEVEL_2, Badge.ITEM_LEVEL_3, Badge.ITEM_LEVEL_4 );
+ leaveBest( filtered, Badge.POTIONS_COOKED_1, Badge.POTIONS_COOKED_2, Badge.POTIONS_COOKED_3, Badge.POTIONS_COOKED_4 );
+ leaveBest( filtered, Badge.BOSS_SLAIN_1_ALL_CLASSES, Badge.BOSS_SLAIN_3_ALL_SUBCLASSES );
+ leaveBest( filtered, Badge.DEATH_FROM_FIRE, Badge.YASD );
+ leaveBest( filtered, Badge.DEATH_FROM_GAS, Badge.YASD );
+ leaveBest( filtered, Badge.DEATH_FROM_HUNGER, Badge.YASD );
+ leaveBest( filtered, Badge.DEATH_FROM_POISON, Badge.YASD );
+ leaveBest( filtered, Badge.VICTORY, Badge.VICTORY_ALL_CLASSES );
+ leaveBest( filtered, Badge.GAMES_PLAYED_1, Badge.GAMES_PLAYED_2, Badge.GAMES_PLAYED_3, Badge.GAMES_PLAYED_4 );
+
+ ArrayList list = new ArrayList( filtered );
+ Collections.sort( list );
+
+ return list;
+ }
+
+ private static void leaveBest( HashSet list, Badge...badges ) {
+ for (int i=badges.length-1; i > 0; i--) {
+ if (list.contains( badges[i])) {
+ for (int j=0; j < i; j++) {
+ list.remove( badges[j] );
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/Bones.java b/src/com/watabou/pixeldungeon/Bones.java
new file mode 100644
index 0000000000..4114784950
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Bones.java
@@ -0,0 +1,125 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.items.Gold;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.rings.Ring;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Bones {
+
+ private static final String BONES_FILE = "bones.dat";
+
+ private static final String LEVEL = "level";
+ private static final String ITEM = "item";
+
+ private static int depth = -1;
+ private static Item item;
+
+ public static void leave() {
+
+ item = null;
+ switch (Random.Int( 4 )) {
+ case 0:
+ item = Dungeon.hero.belongings.weapon;
+ break;
+ case 1:
+ item = Dungeon.hero.belongings.armor;
+ break;
+ case 2:
+ item = Dungeon.hero.belongings.ring1;
+ break;
+ case 3:
+ item = Dungeon.hero.belongings.ring2;
+ break;
+ }
+ if (item == null) {
+ if (Dungeon.gold > 0) {
+ item = new Gold( Random.IntRange( 1, Dungeon.gold ) );
+ } else {
+ item = new Gold( 1 );
+ }
+ }
+
+ depth = Dungeon.depth;
+
+ Bundle bundle = new Bundle();
+ bundle.put( LEVEL, depth );
+ bundle.put( ITEM, item );
+
+ try {
+ OutputStream output = Game.instance.openFileOutput( BONES_FILE, Game.MODE_PRIVATE );
+ Bundle.write( bundle, output );
+ output.close();
+ } catch (IOException e) {
+
+ }
+ }
+
+ public static Item get() {
+ if (depth == -1) {
+
+ try {
+ InputStream input = Game.instance.openFileInput( BONES_FILE ) ;
+ Bundle bundle = Bundle.read( input );
+ input.close();
+
+ depth = bundle.getInt( LEVEL );
+ item = (Item)bundle.get( ITEM );
+
+ return get();
+
+ } catch (IOException e) {
+ return null;
+ }
+
+ } else {
+ if (depth == Dungeon.depth) {
+ Game.instance.deleteFile( BONES_FILE );
+ depth = 0;
+
+ if (!item.stackable) {
+ item.cursed = true;
+ item.cursedKnown = true;
+ if (item.isUpgradable()) {
+ int lvl = (Dungeon.depth - 1) * 3 / 5 + 1;
+ if (lvl < item.level) {
+ item.degrade( item.level - lvl );
+ }
+ item.levelKnown = false;
+ }
+ }
+
+ if (item instanceof Ring) {
+ ((Ring)item).syncGem();
+ }
+
+ return item;
+ } else {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/Chrome.java b/src/com/watabou/pixeldungeon/Chrome.java
new file mode 100644
index 0000000000..ef6d5d93ff
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Chrome.java
@@ -0,0 +1,66 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import com.watabou.noosa.NinePatch;
+
+public class Chrome {
+
+ public enum Type {
+ TOAST,
+ TOAST_TR,
+ WINDOW,
+ BUTTON,
+ TAG,
+ GEM,
+ SCROLL,
+ TAB_SET,
+ TAB_SELECTED,
+ TAB_UNSELECTED,
+ SURFACE
+ };
+
+ public static NinePatch get( Type type ) {
+ switch (type) {
+ case WINDOW:
+ return new NinePatch( Assets.CHROME, 0, 0, 22, 22, 7 );
+ case TOAST:
+ return new NinePatch( Assets.CHROME, 22, 0, 18, 18, 5 );
+ case TOAST_TR:
+ return new NinePatch( Assets.CHROME, 40, 0, 18, 18, 5 );
+ case BUTTON:
+ return new NinePatch( Assets.CHROME, 58, 0, 4, 4, 1 );
+ case TAG:
+ return new NinePatch( Assets.CHROME, 22, 18, 16, 14, 3 );
+ case GEM:
+ return new NinePatch( Assets.CHROME, 0, 32, 32, 32, 13 );
+ case SCROLL:
+ return new NinePatch( Assets.CHROME, 32, 32, 32, 32, 5, 11, 5, 11 );
+ case TAB_SET:
+ return new NinePatch( Assets.CHROME, 64, 0, 22, 22, 7, 7, 7, 7 );
+ case TAB_SELECTED:
+ return new NinePatch( Assets.CHROME, 64, 22, 10, 14, 4, 7, 4, 6 );
+ case TAB_UNSELECTED:
+ return new NinePatch( Assets.CHROME, 74, 22, 10, 14, 4, 7, 4, 6 );
+ case SURFACE:
+ return new NinePatch( Assets.CHROME, 86, 0, 22, 22, 5 );
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/Dungeon.java b/src/com/watabou/pixeldungeon/Dungeon.java
new file mode 100644
index 0000000000..f062208117
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Dungeon.java
@@ -0,0 +1,690 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Amok;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Blacksmith;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Imp;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Ghost;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Wandmaker;
+import com.watabou.pixeldungeon.items.Ankh;
+import com.watabou.pixeldungeon.items.potions.Potion;
+import com.watabou.pixeldungeon.items.rings.Ring;
+import com.watabou.pixeldungeon.items.scrolls.Scroll;
+import com.watabou.pixeldungeon.items.wands.Wand;
+import com.watabou.pixeldungeon.levels.CavesBossLevel;
+import com.watabou.pixeldungeon.levels.CavesLevel;
+import com.watabou.pixeldungeon.levels.CityBossLevel;
+import com.watabou.pixeldungeon.levels.CityLevel;
+import com.watabou.pixeldungeon.levels.DeadEndLevel;
+import com.watabou.pixeldungeon.levels.HallsBossLevel;
+import com.watabou.pixeldungeon.levels.HallsLevel;
+import com.watabou.pixeldungeon.levels.LastLevel;
+import com.watabou.pixeldungeon.levels.LastShopLevel;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.PrisonBossLevel;
+import com.watabou.pixeldungeon.levels.PrisonLevel;
+import com.watabou.pixeldungeon.levels.Room;
+import com.watabou.pixeldungeon.levels.SewerBossLevel;
+import com.watabou.pixeldungeon.levels.SewerLevel;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.scenes.StartScene;
+import com.watabou.pixeldungeon.utils.BArray;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.pixeldungeon.windows.WndResurrect;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.PathFinder;
+import com.watabou.utils.Random;
+
+public class Dungeon {
+
+ private static final String NO_TIPS = "The text is indecipherable...";
+ private static final String[] TIPS = {
+ "Don't overestimate your strength, use weapons and armor you can handle.",
+ "Not all doors in the dungeon are visible at first sight. If you are stuck, search for hidden doors.",
+ "Remember, that raising your strength is not the only way to access better equipment, you can go " +
+ "the other way lowering its strength requirement with Scrolls of Upgrade.",
+ "You can spend your gold in shops on deeper levels of the dungeon. The first one is on the 6th level.",
+
+ "Beware of Goo!",
+
+ "Pixel-Mart - all you need for successful adventure!",
+ "Identify your potions and scrolls as soon as possible. Don't put it off to the moment " +
+ "when you actually need them.",
+ "Being hungry doesn't hurt, but starving does hurt.",
+ "Surprise attack has a better chance to hit. For example, you can ambush your enemy behind " +
+ "a closed door when you know it is approaching.",
+
+ "Don't let The Tengu out!",
+
+ "Pixel-Mart. Spend money. Live longer.",
+ "When you're attacked by several monsters at the same time, try to retreat behind a door.",
+ "If you are burning, you can't put out the fire in the water while levitating.",
+ "There is no sense in possessing more than one Ankh at the same time, because you will lose them upon resurrecting.",
+
+ "DANGER! Heavy machinery can cause injury, loss of limbs or death!",
+
+ "Pixel-Mart. A safer life in dungeon.",
+ "When you upgrade an enchanted weapon, there is a chance to destroy that enchantment.",
+ "In a Well of Transmutation you can get an item, that cannot be obtained otherwise.",
+ "The only way to enchant a weapon is by upgrading it with a Scroll of Weapon Upgrade.",
+
+ "No weapons allowed in the presence of His Majesty!",
+
+ "Pixel-Mart. Special prices for demon hunters!",
+ "The text is written in demonic language.",
+ "The text is written in demonic language.",
+ "The text is written in demonic language."
+ };
+
+ private static final String TXT_DEAD_END =
+ "What are you doing here?!";
+
+ public static int potionOfStrength;
+ public static int scrollsOfUpgrade;
+ public static int arcaneStyli;
+ public static boolean dewVial; // true if the dew vial can be spawned
+ public static int transmutation; // depth number for a well of transmutation
+
+
+ public static Hero hero;
+ public static Level level;
+
+ // Eitherî Item or Class extends Item>
+ public static Object quickslot;
+
+ public static int depth;
+ public static int gold;
+ // Reason of death
+ public static String resultDescription;
+
+ public static HashSet chapters;
+
+ // Hero's field of view
+ public static boolean[] visible = new boolean[Level.LENGTH];
+
+ public static boolean nightMode;
+
+ public static void init() {
+
+ Actor.clear();
+
+ PathFinder.setMapSize( Level.WIDTH, Level.HEIGHT );
+
+ Scroll.initLabels();
+ Potion.initColors();
+ Wand.initWoods();
+ Ring.initGems();
+
+ Statistics.reset();
+ Journal.reset();
+
+ depth = 0;
+ gold = 0;
+
+ potionOfStrength = 0;
+ scrollsOfUpgrade = 0;
+ arcaneStyli = 0;
+ dewVial = true;
+ transmutation = Random.IntRange( 6, 14 );
+
+ chapters = new HashSet();
+
+ Ghost.Quest.reset();
+ Wandmaker.Quest.reset();
+ Blacksmith.Quest.reset();
+ Imp.Quest.reset();
+
+ Room.shuffleTypes();
+
+ hero = new Hero();
+ hero.live();
+
+ Badges.reset();
+
+ StartScene.curClass.initHero( hero );
+ }
+
+ public static Level newLevel() {
+
+ Dungeon.level = null;
+ Actor.clear();
+
+ depth++;
+ if (depth > Statistics.deepestFloor) {
+ Statistics.deepestFloor = depth;
+
+ if (Statistics.qualifiedForNoKilling) {
+ Statistics.completedWithNoKilling = true;
+ } else {
+ Statistics.completedWithNoKilling = false;
+ }
+ }
+
+ Arrays.fill( visible, false );
+
+ Level level;
+ switch (depth) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ level = new SewerLevel();
+ break;
+ case 5:
+ level = new SewerBossLevel();
+ break;
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ level = new PrisonLevel();
+ break;
+ case 10:
+ level = new PrisonBossLevel();
+ break;
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ level = new CavesLevel();
+ break;
+ case 15:
+ level = new CavesBossLevel();
+ break;
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ level = new CityLevel();
+ break;
+ case 20:
+ level = new CityBossLevel();
+ break;
+ case 21:
+ level = new LastShopLevel();
+ break;
+ case 22:
+ case 23:
+ case 24:
+ level = new HallsLevel();
+ break;
+ case 25:
+ level = new HallsBossLevel();
+ break;
+ case 26:
+ level = new LastLevel();
+ break;
+ default:
+ level = new DeadEndLevel();
+ Statistics.deepestFloor--;
+ }
+
+ level.create();
+
+ Statistics.qualifiedForNoKilling = !bossLevel();
+
+ return level;
+ }
+
+ public static void resetLevel() {
+
+ Actor.clear();
+
+ Arrays.fill( visible, false );
+
+ level.reset();
+ switchLevel( level, level.entrance );
+ }
+
+ public static String tip() {
+
+ if (level instanceof DeadEndLevel) {
+
+ return TXT_DEAD_END;
+
+ } else {
+
+ int index = depth - 1;
+
+ if (index < TIPS.length) {
+ return TIPS[index];
+ } else {
+ return NO_TIPS;
+ }
+ }
+ }
+
+ public static boolean shopOnLevel() {
+ return depth == 6 || depth == 11 || depth == 16;
+ }
+
+ public static boolean bossLevel() {
+ return bossLevel( depth );
+ }
+
+ public static boolean bossLevel( int depth ) {
+ return depth == 5 || depth == 10 || depth == 15 || depth == 20 || depth == 25;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static void switchLevel( final Level level, int pos ) {
+
+ nightMode = new Date().getHours() < 7;
+
+ Dungeon.level = level;
+ Actor.init();
+
+ Actor respawner = level.respawner();
+ if (respawner != null) {
+ Actor.add( level.respawner() );
+ }
+
+ hero.pos = pos != -1 ? pos : level.exit;
+
+ Light light = hero.buff( Light.class );
+ hero.viewDistance = light == null ? level.viewDistance : Math.max( Light.DISTANCE, level.viewDistance );
+
+ observe();
+ }
+
+ public static boolean posNeeded() {
+ int[] quota = {4, 2, 9, 4, 14, 6, 19, 8, 24, 9};
+ return chance( quota, potionOfStrength );
+ }
+
+ public static boolean soeNeeded() {
+ int[] quota = {5, 3, 10, 6, 15, 9, 20, 12, 25, 13};
+ return chance( quota, scrollsOfUpgrade );
+ }
+
+ private static boolean chance( int[] quota, int number ) {
+
+ for (int i=0; i < quota.length; i += 2) {
+ int qDepth = quota[i];
+ if (depth <= qDepth) {
+ int qNumber = quota[i + 1];
+ return Random.Float() < (float)(qNumber - number) / (qDepth - depth + 1);
+ }
+ }
+
+ return false;
+ }
+
+ public static boolean asNeeded() {
+ return Random.Int( 12 * (1 + arcaneStyli) ) < depth;
+ }
+
+ private static final String RG_GAME_FILE = "game.dat";
+ private static final String RG_DEPTH_FILE = "depth%d.dat";
+
+ private static final String WR_GAME_FILE = "warrior.dat";
+ private static final String WR_DEPTH_FILE = "warrior%d.dat";
+
+ private static final String MG_GAME_FILE = "mage.dat";
+ private static final String MG_DEPTH_FILE = "mage%d.dat";
+
+ private static final String RN_GAME_FILE = "ranger.dat";
+ private static final String RN_DEPTH_FILE = "ranger%d.dat";
+
+ private static final String VERSION = "version";
+ private static final String HERO = "hero";
+ private static final String GOLD = "gold";
+ private static final String DEPTH = "depth";
+ private static final String QUICKSLOT = "quickslot";
+ private static final String LEVEL = "level";
+ private static final String POS = "potionsOfStrength";
+ private static final String SOU = "scrollsOfEnhancement";
+ private static final String AS = "arcaneStyli";
+ private static final String DV = "dewVial";
+ private static final String WT = "transmutation";
+ private static final String CHAPTERS = "chapters";
+ private static final String QUESTS = "quests";
+ private static final String BADGES = "badges";
+
+ public static String gameFile( HeroClass cl ) {
+ switch (cl) {
+ case WARRIOR:
+ return WR_GAME_FILE;
+ case MAGE:
+ return MG_GAME_FILE;
+ case HUNTRESS:
+ return RN_GAME_FILE;
+ default:
+ return RG_GAME_FILE;
+ }
+ }
+
+ private static String depthFile( HeroClass cl ) {
+ switch (cl) {
+ case WARRIOR:
+ return WR_DEPTH_FILE;
+ case MAGE:
+ return MG_DEPTH_FILE;
+ case HUNTRESS:
+ return RN_DEPTH_FILE;
+ default:
+ return RG_DEPTH_FILE;
+ }
+ }
+
+ public static void saveGame( String fileName ) throws IOException {
+ try {
+ Bundle bundle = new Bundle();
+
+ bundle.put( VERSION, Game.version );
+ bundle.put( HERO, hero );
+ bundle.put( GOLD, gold );
+ bundle.put( DEPTH, depth );
+
+ bundle.put( POS, potionOfStrength );
+ bundle.put( SOU, scrollsOfUpgrade );
+ bundle.put( AS, arcaneStyli );
+ bundle.put( DV, dewVial );
+ bundle.put( WT, transmutation );
+
+ int count = 0;
+ int ids[] = new int[chapters.size()];
+ for (Integer id : chapters) {
+ ids[count++] = id;
+ }
+ bundle.put( CHAPTERS, ids );
+
+ Bundle quests = new Bundle();
+ Ghost .Quest.storeInBundle( quests );
+ Wandmaker .Quest.storeInBundle( quests );
+ Blacksmith .Quest.storeInBundle( quests );
+ Imp .Quest.storeInBundle( quests );
+ bundle.put( QUESTS, quests );
+
+ Room.storeRoomsInBundle( bundle );
+
+ Statistics.storeInBundle( bundle );
+ Journal.storeInBundle( bundle );
+
+ if (quickslot instanceof Class) {
+ bundle.put( QUICKSLOT, ((Class>)quickslot).getName() );
+ }
+
+ Scroll.save( bundle );
+ Potion.save( bundle );
+ Wand.save( bundle );
+ Ring.save( bundle );
+
+ Bundle badges = new Bundle();
+ Badges.saveLocal( badges );
+ bundle.put( BADGES, badges );
+
+ OutputStream output = Game.instance.openFileOutput( fileName, Game.MODE_PRIVATE );
+ Bundle.write( bundle, output );
+ output.close();
+
+ } catch (Exception e) {
+
+ GamesInProgress.setUnknown( hero.heroClass );
+ }
+ }
+
+ public static void saveLevel() throws IOException {
+ Bundle bundle = new Bundle();
+ bundle.put( LEVEL, level );
+
+ OutputStream output = Game.instance.openFileOutput(
+ Utils.format( depthFile( hero.heroClass ), depth ), Game.MODE_PRIVATE );
+ Bundle.write( bundle, output );
+ output.close();
+ }
+
+ public static void saveAll() throws IOException {
+ if (hero.isAlive()) {
+
+ Actor.fixTime();
+ saveGame( gameFile( hero.heroClass ) );
+ saveLevel();
+
+ GamesInProgress.set(
+ hero.heroClass,
+ depth,
+ hero.lvl,
+ hero.belongings.armor != null ? hero.belongings.armor.tier : 0 );
+
+ } else if (WndResurrect.instance != null) {
+
+ WndResurrect.instance.hide();
+ Hero.reallyDie( WndResurrect.causeOfDeath );
+
+ }
+ }
+
+ public static void loadGame( HeroClass cl ) throws IOException {
+ loadGame( gameFile( cl ), true );
+ }
+
+ public static void loadGame( String fileName ) throws IOException {
+ loadGame( fileName, false );
+ }
+
+ public static void loadGame( String fileName, boolean fullLoad ) throws IOException {
+
+ Bundle bundle = gameBundle( fileName );
+
+ Dungeon.level = null;
+ Dungeon.depth = -1;
+
+ if (fullLoad) {
+ PathFinder.setMapSize( Level.WIDTH, Level.HEIGHT );
+ }
+
+ Scroll.restore( bundle );
+ Potion.restore( bundle );
+ Wand.restore( bundle );
+ Ring.restore( bundle );
+
+ potionOfStrength = bundle.getInt( POS );
+ scrollsOfUpgrade = bundle.getInt( SOU );
+ arcaneStyli = bundle.getInt( AS );
+ dewVial = bundle.getBoolean( DV );
+ transmutation = bundle.getInt( WT );
+
+ if (fullLoad) {
+ chapters = new HashSet();
+ int ids[] = bundle.getIntArray( CHAPTERS );
+ if (ids != null) {
+ for (int id : ids) {
+ chapters.add( id );
+ }
+ }
+
+ Bundle quests = bundle.getBundle( QUESTS );
+ if (!quests.isNull()) {
+ Ghost.Quest.restoreFromBundle( quests );
+ Wandmaker.Quest.restoreFromBundle( quests );
+ Blacksmith.Quest.restoreFromBundle( quests );
+ Imp.Quest.restoreFromBundle( quests );
+ } else {
+ Ghost.Quest.reset();
+ Wandmaker.Quest.reset();
+ Blacksmith.Quest.reset();
+ Imp.Quest.reset();
+ }
+
+ Room.restoreRoomsFromBundle( bundle );
+ }
+
+ Bundle badges = bundle.getBundle( BADGES );
+ if (!badges.isNull()) {
+ Badges.loadLocal( badges );
+ } else {
+ Badges.reset();
+ }
+
+ String qsClass = bundle.getString( QUICKSLOT );
+ if (qsClass != null) {
+ try {
+ quickslot = Class.forName( qsClass );
+ } catch (ClassNotFoundException e) {
+ }
+ } else {
+ quickslot = null;
+ }
+
+ @SuppressWarnings("unused")
+ String version = bundle.getString( VERSION );
+
+ hero = null;
+ hero = (Hero)bundle.get( HERO );
+
+ gold = bundle.getInt( GOLD );
+ depth = bundle.getInt( DEPTH );
+
+ Statistics.restoreFromBundle( bundle );
+ Journal.restoreFromBundle( bundle );
+ }
+
+ public static Level loadLevel( HeroClass cl ) throws IOException {
+
+ Dungeon.level = null;
+ Actor.clear();
+
+ InputStream input = Game.instance.openFileInput( Utils.format( depthFile( cl ), depth ) ) ;
+ Bundle bundle = Bundle.read( input );
+ input.close();
+
+ return (Level)bundle.get( "level" );
+ }
+
+ public static void deleteGame( HeroClass cl, boolean deleteLevels ) {
+
+ Game.instance.deleteFile( gameFile( cl ) );
+
+ if (deleteLevels) {
+ int depth = 1;
+ while (Game.instance.deleteFile( Utils.format( depthFile( cl ), depth ) )) {
+ depth++;
+ }
+ }
+
+ GamesInProgress.delete( cl );
+ }
+
+ public static Bundle gameBundle( String fileName ) throws IOException {
+
+ InputStream input = Game.instance.openFileInput( fileName );
+ Bundle bundle = Bundle.read( input );
+ input.close();
+
+ return bundle;
+ }
+
+ public static void preview( GamesInProgress.Info info, Bundle bundle ) {
+ info.depth = bundle.getInt( DEPTH );
+ if (info.depth == -1) {
+ info.depth = bundle.getInt( "maxDepth" ); // <-- It has to be refactored!
+ }
+ Hero.preview( info, bundle.getBundle( HERO ) );
+ }
+
+ public static void fail( String desc ) {
+ resultDescription = desc;
+ if (hero.belongings.getItem( Ankh.class ) == null) {
+ Rankings.INSTANCE.submit( false );
+ }
+ }
+
+ public static void win( String desc ) {
+ resultDescription = desc;
+ Rankings.INSTANCE.submit( true );
+ }
+
+ public static void observe() {
+
+ if (level == null) {
+ return;
+ }
+
+ level.updateFieldOfView( hero );
+ System.arraycopy( Level.fieldOfView, 0, visible, 0, visible.length );
+
+ BArray.or( level.visited, visible, level.visited );
+
+ GameScene.afterObserve();
+ }
+
+ private static boolean[] passable = new boolean[Level.LENGTH];
+
+ public static int findPath( Char ch, int from, int to, boolean pass[], boolean[] visible ) {
+
+ if (Level.adjacent( from, to )) {
+ return Actor.findChar( to ) == null && (pass[to] || Level.avoid[to]) ? to : -1;
+ }
+
+ if (ch.flying || ch.buff( Amok.class ) != null) {
+ BArray.or( pass, Level.avoid, passable );
+ } else {
+ System.arraycopy( pass, 0, passable, 0, Level.LENGTH );
+ }
+
+ for (Actor actor : Actor.all()) {
+ if (actor instanceof Char) {
+ int pos = ((Char)actor).pos;
+ if (visible[pos]) {
+ passable[pos] = false;
+ }
+ }
+ }
+
+ return PathFinder.getStep( from, to, passable );
+
+ }
+
+ public static int flee( Char ch, int cur, int from, boolean pass[], boolean[] visible ) {
+
+ if (ch.flying) {
+ BArray.or( pass, Level.avoid, passable );
+ } else {
+ System.arraycopy( pass, 0, passable, 0, Level.LENGTH );
+ }
+
+ for (Actor actor : Actor.all()) {
+ if (actor instanceof Char) {
+ int pos = ((Char)actor).pos;
+ if (visible[pos]) {
+ passable[pos] = false;
+ }
+ }
+ }
+ passable[cur] = true;
+
+ return PathFinder.getStepBack( cur, from, passable );
+
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/DungeonTilemap.java b/src/com/watabou/pixeldungeon/DungeonTilemap.java
new file mode 100644
index 0000000000..24d7af3ee9
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/DungeonTilemap.java
@@ -0,0 +1,94 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import com.watabou.noosa.Image;
+import com.watabou.noosa.TextureFilm;
+import com.watabou.noosa.Tilemap;
+import com.watabou.noosa.tweeners.AlphaTweener;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.utils.Point;
+import com.watabou.utils.PointF;
+
+public class DungeonTilemap extends Tilemap {
+
+ public static final int SIZE = 16;
+
+ private static DungeonTilemap instance;
+
+ public DungeonTilemap() {
+ super(
+ Dungeon.level.tilesTex(),
+ new TextureFilm( Dungeon.level.tilesTex(), SIZE, SIZE ) );
+ map( Dungeon.level.map, Level.WIDTH );
+
+ instance = this;
+ }
+
+ public int screenToTile( int x, int y ) {
+ Point p = camera().screenToCamera( x, y ).
+ offset( this.point().negate() ).
+ invScale( SIZE ).
+ floor();
+ return p.x >= 0 && p.x < Level.WIDTH && p.y >= 0 && p.y < Level.HEIGHT ? p.x + p.y * Level.WIDTH : -1;
+ }
+
+ @Override
+ public boolean overlapsPoint( float x, float y ) {
+ return true;
+ }
+
+ public void discover( int pos, int oldValue ) {
+
+ final Image tile = tile( oldValue );
+ tile.point( tileToWorld( pos ) );
+
+ // For bright mode
+ tile.rm = tile.gm = tile.bm = rm;
+ tile.ra = tile.ga = tile.ba = ra;
+ parent.add( tile );
+
+ parent.add( new AlphaTweener( tile, 0, 0.6f ) {
+ protected void onComplete() {
+ tile.killAndErase();
+ killAndErase();
+ };
+ } );
+ }
+
+ public static PointF tileToWorld( int pos ) {
+ return new PointF( pos % Level.WIDTH, pos / Level.WIDTH ).scale( SIZE );
+ }
+
+ public static PointF tileCenterToWorld( int pos ) {
+ return new PointF(
+ (pos % Level.WIDTH + 0.5f) * SIZE,
+ (pos / Level.WIDTH + 0.5f) * SIZE );
+ }
+
+ public static Image tile( int index ) {
+ Image img = new Image( instance.texture );
+ img.frame( instance.tileset.get( index ) );
+ return img;
+ }
+
+ @Override
+ public boolean overlapsScreenPoint( int x, int y ) {
+ return true;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/FogOfWar.java b/src/com/watabou/pixeldungeon/FogOfWar.java
new file mode 100644
index 0000000000..39d51b7b31
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/FogOfWar.java
@@ -0,0 +1,121 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.util.Arrays;
+
+import android.graphics.Bitmap;
+
+import com.watabou.gltextures.SmartTexture;
+import com.watabou.gltextures.TextureCache;
+import com.watabou.glwrap.Texture;
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.scenes.GameScene;
+
+public class FogOfWar extends Image {
+
+ private static final int VISIBLE = 0x00000000;
+ private static final int VISITED = 0xcc111111;
+ private static final int MAPPED = 0xcc442211;
+ private static final int INVISIBLE = 0xFF000000;
+
+ private int[] pixels;
+
+ private int pWidth;
+ private int pHeight;
+
+ private int width2;
+ private int height2;
+
+ public FogOfWar( int mapWidth, int mapHeight ) {
+
+ super();
+
+ pWidth = mapWidth + 1;
+ pHeight = mapHeight + 1;
+
+ width2 = 1;
+ while (width2 < pWidth) {
+ width2 <<= 1;
+ }
+
+ height2 = 1;
+ while (height2 < pHeight) {
+ height2 <<= 1;
+ }
+
+ float size = DungeonTilemap.SIZE;
+ width = width2 * size;
+ height = height2 * size;
+
+ texture( new FogTexture() );
+
+ scale.set(
+ DungeonTilemap.SIZE,
+ DungeonTilemap.SIZE );
+
+ x = y = -size / 2;
+ }
+
+ public void updateVisibility( boolean[] visible, boolean[] visited, boolean[] mapped ) {
+
+ if (pixels == null) {
+ pixels = new int[width2 * height2];
+ Arrays.fill( pixels, INVISIBLE );
+ }
+
+ for (int i=1; i < pHeight - 1; i++) {
+ int pos = (pWidth - 1) * i;
+ for (int j=1; j < pWidth - 1; j++) {
+ pos++;
+ int c = INVISIBLE;
+ if (visible[pos] && visible[pos - (pWidth - 1)] &&
+ visible[pos - 1] && visible[pos - (pWidth - 1) - 1]) {
+ c = VISIBLE;
+ } else
+ if (visited[pos] && visited[pos - (pWidth - 1)] &&
+ visited[pos - 1] && visited[pos - (pWidth - 1) - 1]) {
+ c = VISITED;
+ }
+ else
+ if (mapped[pos] && mapped[pos - (pWidth - 1)] &&
+ mapped[pos - 1] && mapped[pos - (pWidth - 1) - 1]) {
+ c = MAPPED;
+ }
+ pixels[i * width2 + j] = c;
+ }
+ }
+
+ texture.pixels( width2, height2, pixels );
+ }
+
+ private class FogTexture extends SmartTexture {
+
+ public FogTexture() {
+ super( Bitmap.createBitmap( width2, height2, Bitmap.Config.ARGB_8888 ) );
+ filter( Texture.LINEAR, Texture.LINEAR );
+ TextureCache.add( FogOfWar.class, this );
+ }
+
+ @Override
+ public void reload() {
+ super.reload();
+ GameScene.afterObserve();
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/GamesInProgress.java b/src/com/watabou/pixeldungeon/GamesInProgress.java
new file mode 100644
index 0000000000..ad5659ea6d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/GamesInProgress.java
@@ -0,0 +1,75 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.util.HashMap;
+
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.utils.Bundle;
+
+public class GamesInProgress {
+
+ private static HashMap state = new HashMap();
+
+ public static Info check( HeroClass cl ) {
+
+ if (state.containsKey( cl )) {
+
+ return state.get( cl );
+
+ } else {
+
+ Info info;
+ try {
+
+ Bundle bundle = Dungeon.gameBundle( Dungeon.gameFile( cl ) );
+ info = new Info();
+ Dungeon.preview( info, bundle );
+
+ } catch (Exception e) {
+ info = null;
+ }
+
+ state.put( cl, info );
+ return info;
+
+ }
+ }
+
+ public static void set( HeroClass cl, int depth, int level, int armor ) {
+ Info info = new Info();
+ info.depth = depth;
+ info.level = level;
+ info.armor = armor;
+ state.put( cl, info );
+ }
+
+ public static void setUnknown( HeroClass cl ) {
+ state.remove( cl );
+ }
+
+ public static void delete( HeroClass cl ) {
+ state.put( cl, null );
+ }
+
+ public static class Info {
+ public int depth;
+ public int level;
+ public int armor;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/Journal.java b/src/com/watabou/pixeldungeon/Journal.java
new file mode 100644
index 0000000000..3edf2b4188
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Journal.java
@@ -0,0 +1,122 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.util.ArrayList;
+
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+
+public class Journal {
+
+ public static enum Feature {
+ WELL_OF_HEALTH ( "Well of Health" ),
+ WELL_OF_AWARENESS ( "Well of Awareness" ),
+ WELL_OF_TRANSMUTATION ( "Well of Transmutation" ),
+ ALCHEMY ( "Alchemy pot" ),
+ GARDEN ( "Garden" ),
+ STATUE ( "Animated statue" ),
+
+ GHOST ( "Sad ghost" ),
+ WANDMAKER ( "Old wandmaker" ),
+ TROLL ( "Troll blacksmith" ),
+ IMP ( "Ambitious imp" );
+
+ public String desc;
+
+ private Feature( String desc ) {
+ this.desc = desc;
+ }
+ };
+
+ public static class Record implements Comparable, Bundlable {
+
+ private static final String FEATURE = "feature";
+ private static final String DEPTH = "depth";
+
+ public Feature feature;
+ public int depth;
+
+ public Record() {
+ }
+
+ public Record( Feature feature, int depth ) {
+ this.feature = feature;
+ this.depth = depth;
+ }
+
+ @Override
+ public int compareTo( Record another ) {
+ return another.depth - depth;
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ feature = Feature.valueOf( bundle.getString( FEATURE ) );
+ depth = bundle.getInt( DEPTH );
+ }
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ bundle.put( FEATURE, feature.toString() );
+ bundle.put( DEPTH, depth );
+ }
+ }
+
+ public static ArrayList records;
+
+ public static void reset() {
+ records = new ArrayList();
+ }
+
+ private static final String JOURNAL = "journal";
+
+ public static void storeInBundle( Bundle bundle ) {
+ bundle.put( JOURNAL, records );
+ }
+
+ public static void restoreFromBundle( Bundle bundle ) {
+ records = new ArrayList();
+ for (Bundlable rec : bundle.getCollection( JOURNAL ) ) {
+ records.add( (Record) rec );
+ }
+ }
+
+ public static void add( Feature feature ) {
+ int size = records.size();
+ for (int i=0; i < size; i++) {
+ Record rec = records.get( i );
+ if (rec.feature == feature && rec.depth == Dungeon.depth) {
+ return;
+ }
+ }
+
+ records.add( new Record( feature, Dungeon.depth ) );
+ }
+
+ public static void remove( Feature feature ) {
+ int size = records.size();
+ for (int i=0; i < size; i++) {
+ Record rec = records.get( i );
+ if (rec.feature == feature && rec.depth == Dungeon.depth) {
+ records.remove( i );
+ return;
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/PixelDungeon.java b/src/com/watabou/pixeldungeon/PixelDungeon.java
new file mode 100644
index 0000000000..6f5af9c539
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/PixelDungeon.java
@@ -0,0 +1,206 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.audio.Music;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.scenes.TitleScene;
+
+public class PixelDungeon extends Game {
+
+ public PixelDungeon() {
+ super( TitleScene.class );
+
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.scrolls.ScrollOfUpgrade.class,
+ "com.watabou.pixeldungeon.items.scrolls.ScrollOfEnhancement" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.actors.blobs.WaterOfHealth.class,
+ "com.watabou.pixeldungeon.actors.blobs.Light" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.rings.RingOfMending.class,
+ "com.watabou.pixeldungeon.items.rings.RingOfRejuvenation" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.wands.WandOfTelekinesis.class,
+ "com.watabou.pixeldungeon.items.wands.WandOfTelekenesis" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.actors.blobs.Foliage.class,
+ "com.watabou.pixeldungeon.actors.blobs.Blooming" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.actors.buffs.Shadows.class,
+ "com.watabou.pixeldungeon.actors.buffs.Rejuvenation" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast.class,
+ "com.watabou.pixeldungeon.items.scrolls.ScrollOfNuclearBlast" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.actors.hero.Hero.class,
+ "com.watabou.pixeldungeon.actors.Hero" );
+ // com.watabou.utils.Bundle.addAlias(
+ // com.watabou.pixeldungeon.items.weapon.missiles.Javelin.class,
+ // "com.watabou.pixeldungeon.items.weapon.missiles.Boomerang" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.actors.mobs.npcs.Shopkeeper.class,
+ "com.watabou.pixeldungeon.actors.mobs.Shopkeeper" );
+ // 1.6.1
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.quest.DriedRose.class,
+ "com.watabou.pixeldungeon.items.DriedRose" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.actors.mobs.npcs.MirrorImage.class,
+ "com.watabou.pixeldungeon.items.scrolls.ScrollOfMirrorImage.MirrorImage" );
+ // 1.6.4
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.rings.RingOfElements.class,
+ "com.watabou.pixeldungeon.items.rings.RingOfCleansing" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.rings.RingOfElements.class,
+ "com.watabou.pixeldungeon.items.rings.RingOfResistance" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.weapon.missiles.Boomerang.class,
+ "com.watabou.pixeldungeon.items.weapon.missiles.RangersBoomerang" );
+ com.watabou.utils.Bundle.addAlias(
+ com.watabou.pixeldungeon.items.rings.RingOfPower.class,
+ "com.watabou.pixeldungeon.items.rings.RingOfEnergy" );
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onCreate( Bundle savedInstanceState ) {
+ super.onCreate( savedInstanceState );
+
+ /* if (android.os.Build.VERSION.SDK_INT >= 19) {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY );
+ }*/
+
+ Display display = instance.getWindowManager().getDefaultDisplay();
+ boolean landscape = display.getWidth() > display.getHeight();
+
+ if (Preferences.INSTANCE.getBoolean( Preferences.KEY_LANDSCAPE, false ) != landscape) {
+ landscape( !landscape );
+ }
+
+ Music.INSTANCE.enable( music() );
+ Sample.INSTANCE.enable( soundFx() );
+ }
+
+ /*
+ * ---> Prefernces
+ */
+
+ public static void landscape( boolean value ) {
+ Game.instance.setRequestedOrientation( value ?
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT );
+ Preferences.INSTANCE.put( Preferences.KEY_LANDSCAPE, value );
+ }
+
+ public static boolean landscape() {
+ return width > height;
+ }
+
+ public static void scaleUp( boolean value ) {
+ Preferences.INSTANCE.put( Preferences.KEY_SCALE_UP, value );
+ switchScene( TitleScene.class );
+ }
+
+ public static boolean scaleUp() {
+ return Preferences.INSTANCE.getBoolean( Preferences.KEY_SCALE_UP, true );
+ }
+
+ public static void zoom( int value ) {
+ Preferences.INSTANCE.put( Preferences.KEY_ZOOM, value );
+ }
+
+ public static int zoom() {
+ return Preferences.INSTANCE.getInt( Preferences.KEY_ZOOM, 0 );
+ }
+
+ public static void music( boolean value ) {
+ Music.INSTANCE.enable( value );
+ Preferences.INSTANCE.put( Preferences.KEY_MUSIC, value );
+ }
+
+ public static boolean music() {
+ return Preferences.INSTANCE.getBoolean( Preferences.KEY_MUSIC, true );
+ }
+
+ public static void soundFx( boolean value ) {
+ Sample.INSTANCE.enable( value );
+ Preferences.INSTANCE.put( Preferences.KEY_SOUND_FX, value );
+ }
+
+ public static boolean soundFx() {
+ return Preferences.INSTANCE.getBoolean( Preferences.KEY_SOUND_FX, true );
+ }
+
+ public static void brightness( boolean value ) {
+ Preferences.INSTANCE.put( Preferences.KEY_BRIGHTNESS, value );
+ if (scene() instanceof GameScene) {
+ ((GameScene)scene()).brightness( value );
+ }
+ }
+
+ public static boolean brightness() {
+ return Preferences.INSTANCE.getBoolean( Preferences.KEY_BRIGHTNESS, false );
+ }
+
+ public static void donated( String value ) {
+ Preferences.INSTANCE.put( Preferences.KEY_DONATED, value );
+ }
+
+ public static String donated() {
+ return Preferences.INSTANCE.getString( Preferences.KEY_DONATED, "" );
+ }
+
+ public static void lastClass( int value ) {
+ Preferences.INSTANCE.put( Preferences.KEY_LAST_CLASS, value );
+ }
+
+ public static int lastClass() {
+ return Preferences.INSTANCE.getInt( Preferences.KEY_LAST_CLASS, 0 );
+ }
+
+ public static void intro( boolean value ) {
+ Preferences.INSTANCE.put( Preferences.KEY_INTRO, value );
+ }
+
+ public static boolean intro() {
+ return Preferences.INSTANCE.getBoolean( Preferences.KEY_INTRO, true );
+ }
+
+ /*
+ * <--- Preferences
+ */
+
+ public static void reportException( Exception e ) {
+ Log.e( "PD", Log.getStackTraceString( e ) );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/Preferences.java b/src/com/watabou/pixeldungeon/Preferences.java
new file mode 100644
index 0000000000..7fbfdbb798
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Preferences.java
@@ -0,0 +1,70 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import com.watabou.noosa.Game;
+
+import android.content.SharedPreferences;
+
+enum Preferences {
+
+ INSTANCE;
+
+ public static final String KEY_LANDSCAPE = "landscape";
+ public static final String KEY_SCALE_UP = "scaleup";
+ public static final String KEY_MUSIC = "music";
+ public static final String KEY_SOUND_FX = "soundfx";
+ public static final String KEY_ZOOM = "zoom";
+ public static final String KEY_LAST_CLASS = "last_class";
+ public static final String KEY_DONATED = "donated";
+ public static final String KEY_INTRO = "intro";
+ public static final String KEY_BRIGHTNESS = "brightness";
+
+ private SharedPreferences prefs;
+
+ private SharedPreferences get() {
+ if (prefs == null) {
+ prefs = Game.instance.getPreferences( Game.MODE_PRIVATE );
+ }
+ return prefs;
+ }
+
+ int getInt( String key, int defValue ) {
+ return get().getInt( key, defValue );
+ }
+
+ boolean getBoolean( String key, boolean defValue ) {
+ return get().getBoolean( key, defValue );
+ }
+
+ String getString( String key, String defValue ) {
+ return get().getString( key, defValue );
+ }
+
+ void put( String key, int value ) {
+ get().edit().putInt( key, value ).commit();
+ }
+
+ void put( String key, boolean value ) {
+ get().edit().putBoolean( key, value ).commit();
+ }
+
+ void put( String key, String value ) {
+ get().edit().putString( key, value ).commit();
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/Rankings.java b/src/com/watabou/pixeldungeon/Rankings.java
new file mode 100644
index 0000000000..638de2c67e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Rankings.java
@@ -0,0 +1,195 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.SystemTime;
+
+public enum Rankings {
+
+ INSTANCE;
+
+ public static final int TABLE_SIZE = 6;
+
+ public static final String RANKINGS_FILE = "rankings.dat";
+ public static final String DETAILS_FILE = "game_%d.dat";
+
+ public ArrayList records;
+ public int lastRecord;
+ public int totalNumber;
+
+ public void submit( boolean win ) {
+
+ load();
+
+ Record rec = new Record();
+
+ rec.info = Dungeon.resultDescription;
+ rec.win = win;
+ rec.heroClass = Dungeon.hero.heroClass;
+ rec.armorTier = Dungeon.hero.tier();
+ rec.score = score( win );
+
+ String gameFile = Utils.format( DETAILS_FILE, SystemTime.now );
+ try {
+ Dungeon.saveGame( gameFile );
+ rec.gameFile = gameFile;
+ } catch (IOException e) {
+ rec.gameFile = "";
+ }
+
+ records.add( rec );
+
+ Collections.sort( records, scoreComparator );
+
+ lastRecord = records.indexOf( rec );
+ int size = records.size();
+ if (size > TABLE_SIZE) {
+
+ Record removedGame;
+ if (lastRecord == size - 1) {
+ removedGame = records.remove( size - 2 );
+ lastRecord--;
+ } else {
+ removedGame = records.remove( size - 1 );
+ }
+
+ if (removedGame.gameFile.length() > 0) {
+ Game.instance.deleteFile( removedGame.gameFile );
+ }
+ }
+
+ totalNumber++;
+
+ Badges.validateGamesPlayed();
+
+ save();
+ }
+
+ private int score( boolean win ) {
+ return (Statistics.goldCollected + Dungeon.hero.lvl * Dungeon.depth * 100) * (win ? 2 : 1);
+ }
+
+ private static final String RECORDS = "records";
+ private static final String LATEST = "latest";
+ private static final String TOTAL = "total";
+
+ public void save() {
+ Bundle bundle = new Bundle();
+ bundle.put( RECORDS, records );
+ bundle.put( LATEST, lastRecord );
+ bundle.put( TOTAL, totalNumber );
+
+ try {
+ OutputStream output = Game.instance.openFileOutput( RANKINGS_FILE, Game.MODE_PRIVATE );
+ Bundle.write( bundle, output );
+ output.close();
+ } catch (Exception e) {
+ }
+ }
+
+ public void load() {
+
+ if (records != null) {
+ return;
+ }
+
+ records = new ArrayList();
+
+ try {
+ InputStream input = Game.instance.openFileInput( RANKINGS_FILE );
+ Bundle bundle = Bundle.read( input );
+ input.close();
+
+ for (Bundlable record : bundle.getCollection( RECORDS )) {
+ records.add( (Record)record );
+ }
+ lastRecord = bundle.getInt( LATEST );
+
+ totalNumber = bundle.getInt( TOTAL );
+ if (totalNumber == 0) {
+ totalNumber = records.size();
+ }
+
+ } catch (Exception e) {
+ }
+ }
+
+ public static class Record implements Bundlable {
+
+ private static final String REASON = "reason";
+ private static final String WIN = "win";
+ private static final String SCORE = "score";
+ private static final String TIER = "tier";
+ private static final String GAME = "gameFile";
+
+ public String info;
+ public boolean win;
+
+ public HeroClass heroClass;
+ public int armorTier;
+
+ public int score;
+
+ public String gameFile;
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+
+ info = bundle.getString( REASON );
+ win = bundle.getBoolean( WIN );
+ score = bundle.getInt( SCORE );
+
+ heroClass = HeroClass.restoreInBundle( bundle );
+ armorTier = bundle.getInt( TIER );
+
+ gameFile = bundle.getString( GAME );
+ }
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+
+ bundle.put( REASON, info );
+ bundle.put( WIN, win );
+ bundle.put( SCORE, score );
+
+ heroClass.storeInBundle( bundle );
+ bundle.put( TIER, armorTier );
+
+ bundle.put( GAME, gameFile );
+ }
+ }
+
+ private static final Comparator scoreComparator = new Comparator() {
+ @Override
+ public int compare( Record lhs, Record rhs ) {
+ return (int)Math.signum( rhs.score - lhs.score );
+ }
+ };
+}
diff --git a/src/com/watabou/pixeldungeon/ResultDescriptions.java b/src/com/watabou/pixeldungeon/ResultDescriptions.java
new file mode 100644
index 0000000000..27248a0826
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/ResultDescriptions.java
@@ -0,0 +1,43 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+public class ResultDescriptions {
+
+ // Mobs
+ public static final String MOB = "Killed by %s on level %d";
+ public static final String BOSS = "Killed by the %s on level %d";
+
+ // Items
+ public static final String WAND = "Killed by your own %s on level %d";
+ public static final String GLYPH = "Killed by the %s on level %d";
+
+ // Dungeon features
+ public static final String TRAP = "Killed by discharge of %s on level %d";
+
+ // Debuffs & blobs
+ public static final String BURNING = "Burned to death on level %d";
+ public static final String HUNGER = "Starved to death on level %d";
+ public static final String POISON = "Died from poison on level %d";
+ public static final String GAS = "Died from toxic gas on level %d";
+ public static final String BLEEDING = "Bled to death on level %d";
+ public static final String OOZE = "Killed by a caustic ooze on level %d";
+ public static final String FALL = "Fell to death on level %d";
+
+ public static final String WIN = "Obtained the Amulet of Yendor";
+}
diff --git a/src/com/watabou/pixeldungeon/Statistics.java b/src/com/watabou/pixeldungeon/Statistics.java
new file mode 100644
index 0000000000..2b74531774
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/Statistics.java
@@ -0,0 +1,96 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon;
+
+import com.watabou.utils.Bundle;
+
+public class Statistics {
+
+ public static int goldCollected;
+ public static int deepestFloor;
+ public static int enemiesSlain;
+ public static int foodEaten;
+ public static int potionsCooked;
+ public static int piranhasKilled;
+ public static int nightHunt;
+ public static int ankhsUsed;
+
+ public static float duration;
+
+ public static boolean qualifiedForNoKilling = false;
+ public static boolean completedWithNoKilling = false;
+
+ public static boolean amuletObtained = false;
+
+ public static void reset() {
+
+ goldCollected = 0;
+ deepestFloor = 0;
+ enemiesSlain = 0;
+ foodEaten = 0;
+ potionsCooked = 0;
+ piranhasKilled = 0;
+ nightHunt = 0;
+ ankhsUsed = 0;
+
+ duration = 0;
+
+ qualifiedForNoKilling = false;
+
+ amuletObtained = false;
+
+ }
+
+ private static final String GOLD = "score";
+ private static final String DEEPEST = "maxDepth";
+ private static final String SLAIN = "enemiesSlain";
+ private static final String FOOD = "foodEaten";
+ private static final String ALCHEMY = "potionsCooked";
+ private static final String PIRANHAS = "priranhas";
+ private static final String NIGHT = "nightHunt";
+ private static final String ANKHS = "ankhsUsed";
+ private static final String DURATION = "duration";
+ private static final String AMULET = "amuletObtained";
+
+ public static void storeInBundle( Bundle bundle ) {
+ bundle.put( GOLD, goldCollected );
+ bundle.put( DEEPEST, deepestFloor );
+ bundle.put( SLAIN, enemiesSlain );
+ bundle.put( FOOD, foodEaten );
+ bundle.put( ALCHEMY, potionsCooked );
+ bundle.put( PIRANHAS, piranhasKilled );
+ bundle.put( NIGHT, nightHunt );
+ bundle.put( ANKHS, ankhsUsed );
+ bundle.put( DURATION, duration );
+ bundle.put( AMULET, amuletObtained );
+ }
+
+ public static void restoreFromBundle( Bundle bundle ) {
+ goldCollected = bundle.getInt( GOLD );
+ deepestFloor = bundle.getInt( DEEPEST );
+ enemiesSlain = bundle.getInt( SLAIN );
+ foodEaten = bundle.getInt( FOOD );
+ potionsCooked = bundle.getInt( ALCHEMY );
+ piranhasKilled = bundle.getInt( PIRANHAS );
+ nightHunt = bundle.getInt( NIGHT );
+ ankhsUsed = bundle.getInt( ANKHS );
+ duration = bundle.getFloat( DURATION );
+ amuletObtained = bundle.getBoolean( AMULET );
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/actors/Actor.java b/src/com/watabou/pixeldungeon/actors/Actor.java
new file mode 100644
index 0000000000..ee6cd9cc74
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/Actor.java
@@ -0,0 +1,219 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Statistics;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+
+public abstract class Actor implements Bundlable {
+
+ public static final float TICK = 1f;
+
+ private float time;
+
+ protected abstract boolean act();
+
+ protected void spend( float time ) {
+ this.time += time;
+ }
+
+ protected void postpone( float time ) {
+ if (this.time < now + time) {
+ this.time = now + time;
+ }
+ }
+
+ protected float cooldown() {
+ return time - now;
+ }
+
+ protected void diactivate() {
+ time = Float.MAX_VALUE;
+ }
+
+ protected void onAdd() {}
+
+ protected void onRemove() {}
+
+ private static final String TIME = "time";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ bundle.put( TIME, time );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ time = bundle.getFloat( TIME );
+ }
+
+ // **********************
+ // *** Static members ***
+
+ private static HashSet all = new HashSet();
+ private static Actor current;
+
+ private static float now = 0;
+
+ private static Char[] chars = new Char[Level.LENGTH];
+
+ public static void clear() {
+
+ now = 0;
+
+ Arrays.fill( chars, null );
+ all.clear();
+ }
+
+ public static void fixTime() {
+
+ if (Dungeon.hero != null && all.contains( Dungeon.hero )) {
+ Statistics.duration += now;
+ }
+
+ float min = Float.MAX_VALUE;
+ for (Actor a : all) {
+ if (a.time < min) {
+ min = a.time;
+ }
+ }
+ for (Actor a : all) {
+ a.time -= min;
+ }
+ now = 0;
+ }
+
+ public static void init() {
+
+ addDelayed( Dungeon.hero, -Float.MIN_VALUE );
+
+ for (Mob mob : Dungeon.level.mobs) {
+ add( mob );
+ }
+
+ for (Blob blob : Dungeon.level.blobs.values()) {
+ add( blob );
+ }
+
+ current = null;
+ }
+
+ public static void occupyCell( Char ch ) {
+ chars[ch.pos] = ch;
+ }
+
+ public static void freeCell( int pos ) {
+ chars[pos] = null;
+ }
+
+ protected static void next() {
+ current = null;
+ }
+
+ public static void process() {
+
+ if (current != null) {
+ return;
+ }
+
+ boolean doNext;
+
+ do {
+ now = Float.MAX_VALUE;
+ current = null;
+
+ Arrays.fill( chars, null );
+
+ for (Actor actor : all) {
+ if (actor.time < now) {
+ now = actor.time;
+ current = actor;
+ }
+
+ if (actor instanceof Char) {
+ Char ch = (Char)actor;
+ chars[ch.pos] = ch;
+ }
+ }
+
+ if (current != null) {
+ doNext = current.act();
+ if (doNext && !Dungeon.hero.isAlive()) {
+ doNext = false;
+ current = null;
+ }
+ } else {
+ doNext = false;
+ }
+
+ } while (doNext);
+ }
+
+ public static void add( Actor actor ) {
+ add( actor, now );
+ }
+
+ public static void addDelayed( Actor actor, float delay ) {
+ add( actor, now + delay );
+ }
+
+ private static void add( Actor actor, float time ) {
+
+ if (all.contains( actor )) {
+ return;
+ }
+
+ all.add( actor );
+ actor.time += time; // (+=) => (=) ?
+ actor.onAdd();
+
+ if (actor instanceof Char) {
+ Char ch = (Char)actor;
+ chars[ch.pos] = ch;
+ for (Buff buff : ch.buffs()) {
+ all.add( buff );
+ buff.onAdd();
+ }
+ }
+ }
+
+ public static void remove( Actor actor ) {
+
+ if (actor != null) {
+ all.remove( actor );
+ actor.onRemove();
+ }
+ }
+
+ public static Char findChar( int pos ) {
+ return chars[pos];
+ }
+
+ public static HashSet all() {
+ return all;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/Char.java b/src/com/watabou/pixeldungeon/actors/Char.java
new file mode 100644
index 0000000000..ba3b862b43
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/Char.java
@@ -0,0 +1,493 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.buffs.Amok;
+import com.watabou.pixeldungeon.actors.buffs.Bleeding;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Cripple;
+import com.watabou.pixeldungeon.actors.buffs.Frost;
+import com.watabou.pixeldungeon.actors.buffs.Invisibility;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.actors.buffs.Shadows;
+import com.watabou.pixeldungeon.actors.buffs.Sleep;
+import com.watabou.pixeldungeon.actors.buffs.Speed;
+import com.watabou.pixeldungeon.actors.buffs.Levitation;
+import com.watabou.pixeldungeon.actors.buffs.MindVision;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.actors.buffs.Slow;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroSubClass;
+import com.watabou.pixeldungeon.actors.mobs.Bestiary;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.particles.PoisonParticle;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.levels.features.Door;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public abstract class Char extends Actor {
+
+ protected static final String TXT_HIT = "%s hit %s";
+ protected static final String TXT_KILL = "%s killed you...";
+ protected static final String TXT_DEFEAT = "%s defeated %s";
+
+ private static final String TXT_YOU_MISSED = "%s %s your attack";
+ private static final String TXT_SMB_MISSED = "%s %s %s's attack";
+
+ private static final String TXT_OUT_OF_PARALYSIS = "The pain snapped %s out of paralysis";
+
+ public int pos = 0;
+
+ public CharSprite sprite;
+
+ public String name = "mob";
+
+ public int HT;
+ public int HP;
+
+ protected float baseSpeed = 1;
+
+ public boolean paralysed = false;
+ public boolean pacified = false;
+ public boolean rooted = false;
+ public boolean flying = false;
+ public int invisible = 0;
+
+ public int viewDistance = 8;
+
+ private HashSet buffs = new HashSet();
+
+ @Override
+ protected boolean act() {
+ Dungeon.level.updateFieldOfView( this );
+ return false;
+ }
+
+ private static final String POS = "pos";
+ private static final String TAG_HP = "HP";
+ private static final String TAG_HT = "HT";
+ private static final String BUFFS = "buffs";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+
+ super.storeInBundle( bundle );
+
+ bundle.put( POS, pos );
+ bundle.put( TAG_HP, HP );
+ bundle.put( TAG_HT, HT );
+ bundle.put( BUFFS, buffs );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+
+ super.restoreFromBundle( bundle );
+
+ pos = bundle.getInt( POS );
+ HP = bundle.getInt( TAG_HP );
+ HT = bundle.getInt( TAG_HT );
+
+ for (Bundlable b : bundle.getCollection( BUFFS )) {
+ if (b != null) {
+ ((Buff)b).attachTo( this );
+ }
+ }
+ }
+
+ public boolean attack( Char enemy ) {
+
+ boolean visibleFight = Dungeon.visible[pos] || Dungeon.visible[enemy.pos];
+
+ if (hit( this, enemy, false )) {
+
+ if (visibleFight) {
+ GLog.i( TXT_HIT, name, enemy.name );
+ }
+
+ // Refactoring needed!
+ int dr = this instanceof Hero && ((Hero)this).usingRanged && ((Hero)this).subClass == HeroSubClass.SNIPER ?
+ 0 : Random.IntRange( 0, enemy.dr() );
+
+ int dmg = damageRoll();
+ int effectiveDamage = Math.max( dmg - dr, 0 );;
+
+ effectiveDamage = attackProc( enemy, effectiveDamage );
+ effectiveDamage = enemy.defenseProc( this, effectiveDamage );
+ enemy.damage( effectiveDamage, this );
+
+ if (visibleFight) {
+ Sample.INSTANCE.play( Assets.SND_HIT, 1, 1, Random.Float( 0.8f, 1.25f ) );
+ }
+
+ if (enemy == Dungeon.hero) {
+ Dungeon.hero.interrupt();
+ }
+
+ enemy.sprite.bloodBurstA( sprite.center(), effectiveDamage );
+ enemy.sprite.flash();
+
+ if (!enemy.isAlive() && visibleFight) {
+ if (enemy == Dungeon.hero) {
+
+ if (Dungeon.hero.killerGlyph != null) {
+
+ Dungeon.fail( Utils.format( ResultDescriptions.GLYPH, Dungeon.hero.killerGlyph.name(), Dungeon.depth ) );
+ GLog.n( TXT_KILL, Dungeon.hero.killerGlyph.name() );
+
+ } else {
+ if (Bestiary.isUnique( this )) {
+ Dungeon.fail( Utils.format( ResultDescriptions.BOSS, name, Dungeon.depth ) );
+ } else {
+ Dungeon.fail( Utils.format( ResultDescriptions.MOB,
+ Utils.indefinite( name ), Dungeon.depth ) );
+ }
+
+ GLog.n( TXT_KILL, name );
+ }
+
+ } else {
+ GLog.i( TXT_DEFEAT, name, enemy.name );
+ }
+ }
+
+ return true;
+
+ } else {
+
+ if (visibleFight) {
+ String defense = enemy.defenseVerb();
+ enemy.sprite.showStatus( CharSprite.NEUTRAL, defense );
+ if (this == Dungeon.hero) {
+ GLog.i( TXT_YOU_MISSED, enemy.name, defense );
+ } else {
+ GLog.i( TXT_SMB_MISSED, enemy.name, defense, name );
+ }
+
+ Sample.INSTANCE.play( Assets.SND_MISS );
+ }
+
+ return false;
+
+ }
+ }
+
+ public static boolean hit( Char attacker, Char defender, boolean magic ) {
+ float acuRoll = Random.Float( attacker.attackSkill( defender ) );
+ float defRoll = Random.Float( defender.defenseSkill( attacker ) );
+ return (magic ? acuRoll * 2 : acuRoll) >= defRoll;
+ }
+
+ public int attackSkill( Char target ) {
+ return 0;
+ }
+
+ public int defenseSkill( Char enemy ) {
+ return 0;
+ }
+
+ public String defenseVerb() {
+ return "dodged";
+ }
+
+ public int dr() {
+ return 0;
+ }
+
+ public int damageRoll() {
+ return 1;
+ }
+
+ public int attackProc( Char enemy, int damage ) {
+ return damage;
+ }
+
+ public int defenseProc( Char enemy, int damage ) {
+ return damage;
+ }
+
+ public float speed() {
+ return buff( Cripple.class ) == null ? baseSpeed : baseSpeed * 0.5f;
+ }
+
+ public void damage( int dmg, Object src ) {
+
+ if (HP <= 0) {
+ return;
+ }
+
+ Buff.detach( this, Frost.class );
+
+ Class> srcClass = src.getClass();
+ if (immunities().contains( srcClass )) {
+ dmg = 0;
+ } else if (resistances().contains( srcClass )) {
+ dmg = Random.IntRange( 0, dmg );
+ }
+
+ if (buff( Paralysis.class ) != null) {
+ if (Random.Int( dmg ) >= Random.Int( HP )) {
+ Buff.detach( this, Paralysis.class );
+ if (Dungeon.visible[pos]) {
+ GLog.i( TXT_OUT_OF_PARALYSIS, name );
+ }
+ }
+ }
+
+ HP -= dmg;
+ if (dmg > 0 || src instanceof Char) {
+ sprite.showStatus( HP > HT / 2 ?
+ CharSprite.WARNING :
+ CharSprite.NEGATIVE,
+ Integer.toString( dmg ) );
+ }
+ if (HP <= 0) {
+ die( src );
+ }
+ }
+
+ public void destroy() {
+ HP = 0;
+ Actor.remove( this );
+ Actor.freeCell( pos );
+ }
+
+ public void die( Object src ) {
+ destroy();
+ sprite.die();
+ }
+
+ public boolean isAlive() {
+ return HP > 0;
+ }
+
+ @Override
+ protected void spend( float time ) {
+
+ float timeScale = 1f;
+ if (buff( Slow.class ) != null) {
+ timeScale *= 0.5f;
+ }
+ if (buff( Speed.class ) != null) {
+ timeScale *= 2.0f;
+ }
+
+ super.spend( time / timeScale );
+ }
+
+ public HashSet buffs() {
+ return buffs;
+ }
+
+ @SuppressWarnings("unchecked")
+ public HashSet buffs( Class c ) {
+ HashSet filtered = new HashSet();
+ for (Buff b : buffs) {
+ if (c.isInstance( b )) {
+ filtered.add( (T)b );
+ }
+ }
+ return filtered;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T buff( Class c ) {
+ for (Buff b : buffs) {
+ if (c.isInstance( b )) {
+ return (T)b;
+ }
+ }
+ return null;
+ }
+
+
+ public void add( Buff buff ) {
+
+ buffs.add( buff );
+ Actor.add( buff );
+
+ if (sprite != null) {
+ if (buff instanceof Poison) {
+
+ CellEmitter.center( pos ).burst( PoisonParticle.SPLASH, 5 );
+ sprite.showStatus( CharSprite.NEGATIVE, "poisoned" );
+
+ } else if (buff instanceof Amok) {
+
+ sprite.showStatus( CharSprite.NEGATIVE, "amok" );
+
+ } else if (buff instanceof Slow) {
+
+ sprite.showStatus( CharSprite.NEGATIVE, "slowed" );
+
+ } else if (buff instanceof MindVision) {
+
+ sprite.showStatus( CharSprite.POSITIVE, "mind" );
+ sprite.showStatus( CharSprite.POSITIVE, "vision" );
+
+ } else if (buff instanceof Paralysis) {
+
+ sprite.add( CharSprite.State.PARALYSED );
+ sprite.showStatus( CharSprite.NEGATIVE, "paralysed" );
+
+ } else if (buff instanceof Terror) {
+
+ sprite.showStatus( CharSprite.NEGATIVE, "frightened" );
+
+ } else if (buff instanceof Roots) {
+
+ sprite.showStatus( CharSprite.NEGATIVE, "rooted" );
+
+ } else if (buff instanceof Cripple) {
+
+ sprite.showStatus( CharSprite.NEGATIVE, "crippled" );
+
+ } else if (buff instanceof Bleeding) {
+
+ sprite.showStatus( CharSprite.NEGATIVE, "bleeding" );
+
+ } else if (buff instanceof Sleep) {
+ sprite.idle();
+ }
+
+ else if (buff instanceof Burning) {
+ sprite.add( CharSprite.State.BURNING );
+ } else if (buff instanceof Levitation) {
+ sprite.add( CharSprite.State.LEVITATING );
+ } else if (buff instanceof Frost) {
+ sprite.add( CharSprite.State.FROZEN );
+ } else if (buff instanceof Invisibility) {
+ if (!(buff instanceof Shadows)) {
+ sprite.showStatus( CharSprite.POSITIVE, "invisible" );
+ }
+ sprite.add( CharSprite.State.INVISIBLE );
+ }
+ }
+ }
+
+ public void remove( Buff buff ) {
+
+ buffs.remove( buff );
+ Actor.remove( buff );
+
+ if (buff instanceof Burning) {
+ sprite.remove( CharSprite.State.BURNING );
+ } else if (buff instanceof Levitation) {
+ sprite.remove( CharSprite.State.LEVITATING );
+ } else if (buff instanceof Invisibility && invisible <= 0) {
+ sprite.remove( CharSprite.State.INVISIBLE );
+ } else if (buff instanceof Paralysis) {
+ sprite.remove( CharSprite.State.PARALYSED );
+ } else if (buff instanceof Frost) {
+ sprite.remove( CharSprite.State.FROZEN );
+ }
+ }
+
+ public void remove( Class extends Buff> buffClass ) {
+ for (Buff buff : buffs( buffClass )) {
+ remove( buff );
+ }
+ }
+
+ @Override
+ protected void onRemove() {
+ for (Buff buff : buffs.toArray( new Buff[0] )) {
+ buff.detach();
+ }
+ }
+
+ public void updateSpriteState() {
+ for (Buff buff:buffs) {
+ if (buff instanceof Burning) {
+ sprite.add( CharSprite.State.BURNING );
+ } else if (buff instanceof Levitation) {
+ sprite.add( CharSprite.State.LEVITATING );
+ } else if (buff instanceof Invisibility) {
+ sprite.add( CharSprite.State.INVISIBLE );
+ } else if (buff instanceof Paralysis) {
+ sprite.add( CharSprite.State.PARALYSED );
+ } else if (buff instanceof Frost) {
+ sprite.add( CharSprite.State.FROZEN );
+ } else if (buff instanceof Light) {
+ sprite.add( CharSprite.State.ILLUMINATED );
+ }
+ }
+ }
+
+ public int stealth() {
+ return 0;
+ }
+
+ public void move( int step ) {
+ if (Dungeon.level.map[pos] == Terrain.OPEN_DOOR) {
+ Door.leave( pos );
+ }
+
+ pos = step;
+
+ if (flying && Dungeon.level.map[pos] == Terrain.DOOR) {
+ Door.enter( pos );
+ }
+
+ if (this != Dungeon.hero) {
+ sprite.visible = Dungeon.visible[pos];
+ }
+ }
+
+ public int distance( Char other ) {
+ return Level.distance( pos, other.pos );
+ }
+
+ public void onMotionComplete() {
+ next();
+ }
+
+ public void onAttackComplete() {
+ next();
+ }
+
+ public void onOperateComplete() {
+ next();
+ }
+
+ private static final HashSet> EMPTY = new HashSet>();
+
+ public HashSet> resistances() {
+ return EMPTY;
+ }
+
+ public HashSet> immunities() {
+ return EMPTY;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Alchemy.java b/src/com/watabou/pixeldungeon/actors/blobs/Alchemy.java
new file mode 100644
index 0000000000..fc02374af6
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Alchemy.java
@@ -0,0 +1,76 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.utils.Bundle;
+
+public class Alchemy extends Blob {
+
+ protected int pos;
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+
+ for (int i=0; i < LENGTH; i++) {
+ if (cur[i] > 0) {
+ pos = i;
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void evolve() {
+ volume = off[pos] = cur[pos];
+
+ if (Dungeon.visible[pos]) {
+ Journal.add( Journal.Feature.ALCHEMY );
+ }
+ }
+
+ @Override
+ public void seed( int cell, int amount ) {
+ cur[pos] = 0;
+ pos = cell;
+ volume = cur[pos] = amount;
+ }
+
+ public static void transmute( int cell ) {
+ Heap heap = Dungeon.level.heaps.get( cell );
+ if (heap != null) {
+
+ Item result = heap.transmute();
+ if (result != null) {
+ Dungeon.level.drop( result, cell ).sprite.drop( cell );
+ }
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+ emitter.start( Speck.factory( Speck.BUBBLE ), 0.4f, 0 );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Blob.java b/src/com/watabou/pixeldungeon/actors/blobs/Blob.java
new file mode 100644
index 0000000000..f92db95370
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Blob.java
@@ -0,0 +1,212 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import java.util.Arrays;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.PixelDungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.utils.BArray;
+import com.watabou.utils.Bundle;
+
+public class Blob extends Actor {
+
+ public static final int WIDTH = Level.WIDTH;
+ public static final int HEIGHT = Level.HEIGHT;
+ public static final int LENGTH = Level.LENGTH;
+
+ public int volume = 0;
+
+ public int[] cur;
+ protected int[] off;
+
+ public BlobEmitter emitter;
+
+ protected Blob() {
+
+ cur = new int[LENGTH];
+ off = new int[LENGTH];
+
+ volume = 0;
+ }
+
+ private static final String CUR = "cur";
+ private static final String START = "start";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+
+ if (volume > 0) {
+
+ int start;
+ for (start=0; start < LENGTH; start++) {
+ if (cur[start] > 0) {
+ break;
+ }
+ }
+ int end;
+ for (end=LENGTH-1; end > start; end--) {
+ if (cur[end] > 0) {
+ break;
+ }
+ }
+
+ bundle.put( START, start );
+ bundle.put( CUR, trim( start, end + 1 ) );
+
+ }
+ }
+
+ private int[] trim( int start, int end ) {
+ int len = end - start;
+ int[] copy = new int[len];
+ System.arraycopy( cur, start, copy, 0, len );
+ return copy;
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+
+ super.restoreFromBundle( bundle );
+
+ int[] data = bundle.getIntArray( CUR );
+ if (data != null) {
+ int start = bundle.getInt( START );
+ for (int i=0; i < data.length; i++) {
+ cur[i + start] = data[i];
+ volume += data[i];
+ }
+ }
+
+ if (Level.resizingNeeded) {
+ int[] cur = new int[Level.LENGTH];
+ Arrays.fill( cur, 0 );
+
+ int loadedMapSize = Level.loadedMapSize;
+ for (int i=0; i < loadedMapSize; i++) {
+ System.arraycopy( this.cur, i * loadedMapSize, cur, i * Level.WIDTH, loadedMapSize );
+ }
+
+ this.cur = cur;
+ }
+ }
+
+ @Override
+ public boolean act() {
+
+ spend( TICK );
+
+ if (volume > 0) {
+
+ volume = 0;
+ evolve();
+
+ int[] tmp = off;
+ off = cur;
+ cur = tmp;
+
+ }
+
+ return true;
+ }
+
+ public void use( BlobEmitter emitter ) {
+ this.emitter = emitter;
+ }
+
+ protected void evolve() {
+
+ boolean[] notBlocking = BArray.not( Level.solid, null );
+
+ for (int i=1; i < HEIGHT-1; i++) {
+
+ int from = i * WIDTH + 1;
+ int to = from + WIDTH - 2;
+
+ for (int pos=from; pos < to; pos++) {
+ if (notBlocking[pos]) {
+
+ int count = 1;
+ int sum = cur[pos];
+
+ if (notBlocking[pos-1]) {
+ sum += cur[pos-1];
+ count++;
+ }
+ if (notBlocking[pos+1]) {
+ sum += cur[pos+1];
+ count++;
+ }
+ if (notBlocking[pos-WIDTH]) {
+ sum += cur[pos-WIDTH];
+ count++;
+ }
+ if (notBlocking[pos+WIDTH]) {
+ sum += cur[pos+WIDTH];
+ count++;
+ }
+
+ int value = sum >= count ? (sum / count) - 1 : 0;
+ off[pos] = value;
+
+ volume += value;
+ } else {
+ off[pos] = 0;
+ }
+ }
+ }
+ }
+
+ public void seed( int cell, int amount ) {
+ cur[cell] += amount;
+ volume += amount;
+ }
+
+ public void clear( int cell ) {
+ volume -= cur[cell];
+ cur[cell] = 0;
+ }
+
+ public String tileDesc() {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T seed( int cell, int amount, Class type ) {
+ try {
+
+ T gas = (T)Dungeon.level.blobs.get( type );
+ if (gas == null) {
+ gas = type.newInstance();
+ Dungeon.level.blobs.put( type, gas );
+ }
+
+ gas.seed( cell, amount );
+
+ return gas;
+
+ } catch (Exception e) {
+ PixelDungeon.reportException( e );
+ return null;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Fire.java b/src/com/watabou/pixeldungeon/actors/blobs/Fire.java
new file mode 100644
index 0000000000..79fe55a98e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Fire.java
@@ -0,0 +1,114 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.particles.FlameParticle;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.scenes.GameScene;
+
+public class Fire extends Blob {
+
+ @Override
+ protected void evolve() {
+
+ boolean[] flamable = Level.flamable;
+
+ int from = WIDTH + 1;
+ int to = Level.LENGTH - WIDTH - 1;
+
+ boolean observe = false;
+
+ for (int pos=from; pos < to; pos++) {
+
+ int fire;
+
+ if (cur[pos] > 0) {
+
+ burn( pos );
+
+ fire = cur[pos] - 1;
+ if (fire <= 0 && flamable[pos]) {
+
+ int oldTile = Dungeon.level.map[pos];
+ Level.set( pos, Terrain.EMBERS );
+
+ observe = true;
+ GameScene.updateMap( pos );
+ if (Dungeon.visible[pos]) {
+ GameScene.discoverTile( pos, oldTile );
+ }
+ }
+
+ } else {
+
+ if (flamable[pos] && (cur[pos-1] > 0 || cur[pos+1] > 0 || cur[pos-WIDTH] > 0 || cur[pos+WIDTH] > 0)) {
+ fire = 4;
+ burn( pos );
+ } else {
+ fire = 0;
+ }
+
+ }
+
+ volume += (off[pos] = fire);
+
+ }
+
+ if (observe) {
+ Dungeon.observe();
+ }
+ }
+
+ private void burn( int pos ) {
+ Char ch = Actor.findChar( pos );
+ if (ch != null) {
+ Buff.affect( ch, Burning.class ).reignite( ch );
+ }
+
+ Heap heap = Dungeon.level.heaps.get( pos );
+ if (heap != null) {
+ heap.burn();
+ }
+ }
+
+ public void seed( int cell, int amount ) {
+ if (cur[cell] == 0) {
+ volume += amount;
+ cur[cell] = amount;
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+ emitter.start( FlameParticle.FACTORY, 0.03f, 0 );
+ }
+
+ @Override
+ public String tileDesc() {
+ return "A fire is raging here.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Foliage.java b/src/com/watabou/pixeldungeon/actors/blobs/Foliage.java
new file mode 100644
index 0000000000..f6099c31a3
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Foliage.java
@@ -0,0 +1,86 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Shadows;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.particles.ShaftParticle;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.scenes.GameScene;
+
+public class Foliage extends Blob {
+
+ @Override
+ protected void evolve() {
+
+ int from = WIDTH + 1;
+ int to = Level.LENGTH - WIDTH - 1;
+
+ int[] map = Dungeon.level.map;
+ boolean regrowth = false;
+
+ boolean visible = false;
+
+ for (int pos=from; pos < to; pos++) {
+ if (cur[pos] > 0) {
+
+ off[pos] = cur[pos];
+ volume += off[pos];
+
+ if (map[pos] == Terrain.EMBERS) {
+ map[pos] = Terrain.GRASS;
+ regrowth = true;
+ }
+
+ visible = visible || Dungeon.visible[pos];
+
+ } else {
+ off[pos] = 0;
+ }
+ }
+
+ Hero hero = Dungeon.hero;
+ if (hero.isAlive() && hero.visibleEnemies() == 0 && cur[hero.pos] > 0) {
+ Buff.affect( hero, Shadows.class ).prolong();
+ }
+
+ if (regrowth) {
+ GameScene.updateMap();
+ }
+
+ if (visible) {
+ Journal.add( Journal.Feature.GARDEN );
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+ emitter.start( ShaftParticle.FACTORY, 0.9f, 0 );
+ }
+
+ @Override
+ public String tileDesc() {
+ return "Shafts of light pierce the gloom of the underground garden.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Freezing.java b/src/com/watabou/pixeldungeon/actors/blobs/Freezing.java
new file mode 100644
index 0000000000..9ec2aeccbd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Freezing.java
@@ -0,0 +1,56 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Frost;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.particles.SnowParticle;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.utils.Random;
+
+public class Freezing {
+
+ // It's not really a blob...
+
+ public static void affect( int cell, Fire fire ) {
+
+ Char ch = Actor.findChar( cell );
+ if (ch != null) {
+ Buff.prolong( ch, Frost.class, Frost.duration( ch ) * Random.Float( 1.0f, 1.5f ) );
+ }
+
+ if (fire != null) {
+ fire.clear( cell );
+ }
+
+ Heap heap = Dungeon.level.heaps.get( cell );
+ if (heap != null) {
+ heap.freeze();
+ }
+
+ if (Dungeon.visible[cell]) {
+ CellEmitter.get( cell ).start( SnowParticle.FACTORY, 0.2f, 6 );
+ }
+
+
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/ParalyticGas.java b/src/com/watabou/pixeldungeon/actors/blobs/ParalyticGas.java
new file mode 100644
index 0000000000..cae7c22381
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/ParalyticGas.java
@@ -0,0 +1,52 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+
+public class ParalyticGas extends Blob {
+
+ @Override
+ protected void evolve() {
+ super.evolve();
+
+ Char ch;
+ for (int i=0; i < LENGTH; i++) {
+ if (cur[i] > 0 && (ch = Actor.findChar( i )) != null) {
+ Buff.prolong( ch, Paralysis.class, Paralysis.duration( ch ) );
+ }
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+
+ emitter.pour( Speck.factory( Speck.PARALYSIS ), 0.6f );
+ }
+
+ @Override
+ public String tileDesc() {
+ return "A cloud of paralytic gas is swirling here.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Regrowth.java b/src/com/watabou/pixeldungeon/actors/blobs/Regrowth.java
new file mode 100644
index 0000000000..794c0248ac
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Regrowth.java
@@ -0,0 +1,76 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.particles.LeafParticle;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.scenes.GameScene;
+
+public class Regrowth extends Blob {
+
+ @Override
+ protected void evolve() {
+ super.evolve();
+
+ if (volume > 0) {
+
+ boolean mapUpdated = false;
+
+ for (int i=0; i < LENGTH; i++) {
+ if (off[i] > 0) {
+ int c = Dungeon.level.map[i];
+ if (c == Terrain.EMPTY || c == Terrain.EMBERS || c == Terrain.EMPTY_DECO) {
+
+ Level.set( i, cur[i] > 9 ? Terrain.HIGH_GRASS : Terrain.GRASS );
+ mapUpdated = true;
+
+ } else if (c == Terrain.GRASS && cur[i] > 9) {
+
+ Level.set( i, Terrain.HIGH_GRASS );
+ mapUpdated = true;
+
+ }
+
+ Char ch = Actor.findChar( i );
+ if (ch != null) {
+ Buff.prolong( ch, Roots.class, TICK );
+ }
+ }
+ }
+
+ if (mapUpdated) {
+ GameScene.updateMap();
+ Dungeon.observe();
+ }
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+
+ emitter.start( LeafParticle.LEVEL_SPECIFIC, 0.2f, 0 );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/ToxicGas.java b/src/com/watabou/pixeldungeon/actors/blobs/ToxicGas.java
new file mode 100644
index 0000000000..2db7044227
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/ToxicGas.java
@@ -0,0 +1,93 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+
+public class ToxicGas extends Blob implements Hero.Doom {
+
+ @Override
+ protected void evolve() {
+ super.evolve();
+
+ int levelDamage = 5 + Dungeon.depth * 5;
+
+ Char ch;
+ for (int i=0; i < LENGTH; i++) {
+ if (cur[i] > 0 && (ch = Actor.findChar( i )) != null) {
+
+ int damage = (ch.HT + levelDamage) / 40;
+ if (damage < 1) {
+ damage = 1;
+ }
+
+ ch.damage( damage, this );
+ }
+ }
+
+ Blob blob = Dungeon.level.blobs.get( ParalyticGas.class );
+ if (blob != null) {
+
+ int par[] = blob.cur;
+
+ for (int i=0; i < LENGTH; i++) {
+
+ int t = cur[i];
+ int p = par[i];
+
+ if (p >= t) {
+ volume -= t;
+ cur[i] = 0;
+ } else {
+ blob.volume -= p;
+ par[i] = 0;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+
+ emitter.pour( Speck.factory( Speck.TOXIC ), 0.6f );
+ }
+
+ @Override
+ public String tileDesc() {
+ return "A greenish cloud of toxic gas is swirling here.";
+ }
+
+ @Override
+ public void onDeath() {
+
+ Badges.validateDeathFromGas();
+
+ Dungeon.fail( Utils.format( ResultDescriptions.GAS, Dungeon.depth ) );
+ GLog.n( "You died from a toxic gas.." );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/WaterOfAwareness.java b/src/com/watabou/pixeldungeon/actors/blobs/WaterOfAwareness.java
new file mode 100644
index 0000000000..4a589a3ce3
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/WaterOfAwareness.java
@@ -0,0 +1,108 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.Journal.Feature;
+import com.watabou.pixeldungeon.actors.buffs.Awareness;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.Identification;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.utils.GLog;
+
+public class WaterOfAwareness extends WellWater {
+
+ private static final String TXT_PROCCED =
+ "As you take a sip, you feel the knowledge pours into your mind. " +
+ "Now you know everything about your equipped items. Also you sense " +
+ "all items on the level and know all its secrets.";
+
+ @Override
+ protected boolean affectHero( Hero hero ) {
+
+ Sample.INSTANCE.play( Assets.SND_DRINK );
+ emitter.parent.add( new Identification( DungeonTilemap.tileCenterToWorld( pos ) ) );
+
+ hero.belongings.observe();
+
+ for (int i=0; i < Level.LENGTH; i++) {
+
+ int terr = Dungeon.level.map[i];
+ if ((Terrain.flags[terr] & Terrain.SECRET) != 0) {
+
+ Level.set( i, Terrain.discover( terr ) );
+ GameScene.updateMap( i );
+
+ if (Dungeon.visible[i]) {
+ GameScene.discoverTile( i, terr );
+ }
+ }
+ }
+
+ Buff.affect( hero, Awareness.class, Awareness.DURATION );
+ Dungeon.observe();
+
+ Dungeon.hero.interrupt();
+
+ GLog.p( TXT_PROCCED );
+
+ Journal.remove( Feature.WELL_OF_AWARENESS );
+
+ return true;
+ }
+
+ @Override
+ protected Item affectItem( Item item ) {
+ if (item.isIdentified()) {
+ return null;
+ } else {
+ item.identify();
+ Badges.validateItemLevelAquired( item );
+
+ emitter.parent.add( new Identification( DungeonTilemap.tileCenterToWorld( pos ) ) );
+
+ Journal.remove( Feature.WELL_OF_AWARENESS );
+
+ return item;
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+ emitter.pour( Speck.factory( Speck.QUESTION ), 0.3f );
+ }
+
+ @Override
+ public String tileDesc() {
+ return
+ "Power of knowledge radiates from the water of this well. " +
+ "Take a sip from it to reveal all secrets of equipped items.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/WaterOfHealth.java b/src/com/watabou/pixeldungeon/actors/blobs/WaterOfHealth.java
new file mode 100644
index 0000000000..0710151e02
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/WaterOfHealth.java
@@ -0,0 +1,83 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.Journal.Feature;
+import com.watabou.pixeldungeon.actors.buffs.Hunger;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.effects.particles.ShaftParticle;
+import com.watabou.pixeldungeon.items.DewVial;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.potions.PotionOfHealing;
+import com.watabou.pixeldungeon.utils.GLog;
+
+public class WaterOfHealth extends WellWater {
+
+ private static final String TXT_PROCCED =
+ "As you take a sip, you feel your wounds heal completely.";
+
+ @Override
+ protected boolean affectHero( Hero hero ) {
+
+ Sample.INSTANCE.play( Assets.SND_DRINK );
+
+ PotionOfHealing.heal( hero );
+ hero.belongings.uncurseEquipped();
+ ((Hunger)hero.buff( Hunger.class )).satisfy( Hunger.STARVING );
+
+ CellEmitter.get( pos ).start( ShaftParticle.FACTORY, 0.2f, 3 );
+
+ Dungeon.hero.interrupt();
+
+ GLog.p( TXT_PROCCED );
+
+ Journal.remove( Feature.WELL_OF_HEALTH );
+
+ return true;
+ }
+
+ @Override
+ protected Item affectItem( Item item ) {
+ if (item instanceof DewVial && !((DewVial)item).isFull()) {
+ ((DewVial)item).fill();
+ return item;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+ emitter.start( Speck.factory( Speck.HEALING ), 0.5f, 0 );
+ }
+
+ @Override
+ public String tileDesc() {
+ return
+ "Power of health radiates from the water of this well. " +
+ "Take a sip from it to heal your wounds and satisfy hunger.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/WaterOfTransmutation.java b/src/com/watabou/pixeldungeon/actors/blobs/WaterOfTransmutation.java
new file mode 100644
index 0000000000..07c82c348d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/WaterOfTransmutation.java
@@ -0,0 +1,237 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.Journal.Feature;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.Generator.Category;
+import com.watabou.pixeldungeon.items.potions.Potion;
+import com.watabou.pixeldungeon.items.potions.PotionOfMight;
+import com.watabou.pixeldungeon.items.potions.PotionOfStrength;
+import com.watabou.pixeldungeon.items.rings.Ring;
+import com.watabou.pixeldungeon.items.scrolls.Scroll;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfUpgrade;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfWeaponUpgrade;
+import com.watabou.pixeldungeon.items.wands.Wand;
+import com.watabou.pixeldungeon.items.weapon.Weapon.Enchantment;
+import com.watabou.pixeldungeon.items.weapon.melee.*;
+import com.watabou.pixeldungeon.plants.Plant;
+
+public class WaterOfTransmutation extends WellWater {
+
+ @Override
+ protected Item affectItem( Item item ) {
+
+ if (item instanceof MeleeWeapon) {
+
+ return changeWeapon( (MeleeWeapon)item );
+
+ } else if (item instanceof Scroll) {
+
+ Journal.remove( Feature.WELL_OF_TRANSMUTATION );
+ return changeScroll( (Scroll)item );
+
+ } else if (item instanceof Potion) {
+
+ Journal.remove( Feature.WELL_OF_TRANSMUTATION );
+ return changePotion( (Potion)item );
+
+ } else if (item instanceof Ring) {
+
+ Journal.remove( Feature.WELL_OF_TRANSMUTATION );
+ return changeRing( (Ring)item );
+
+ } else if (item instanceof Wand) {
+
+ Journal.remove( Feature.WELL_OF_TRANSMUTATION );
+ return changeWand( (Wand)item );
+
+ } else if (item instanceof Plant.Seed) {
+
+ Journal.remove( Feature.WELL_OF_TRANSMUTATION );
+ return changeSeed( (Plant.Seed)item );
+
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+ emitter.start( Speck.factory( Speck.CHANGE ), 0.2f, 0 );
+ }
+
+ private MeleeWeapon changeWeapon( MeleeWeapon w ) {
+
+ MeleeWeapon n = null;
+
+ if (w instanceof Knuckles) {
+ n = new Dagger();
+ } else if (w instanceof Dagger) {
+ n = new Knuckles();
+ }
+
+ else if (w instanceof Spear) {
+ n = new Quarterstaff();
+ } else if (w instanceof Quarterstaff) {
+ n = new Spear();
+ }
+
+ else if (w instanceof Sword) {
+ n = new Mace();
+ } else if (w instanceof Mace) {
+ n = new Sword();
+ }
+
+ else if (w instanceof Longsword) {
+ n = new BattleAxe();
+ } else if (w instanceof BattleAxe) {
+ n = new Longsword();
+ }
+
+ else if (w instanceof Glaive) {
+ n = new WarHammer();
+ } else if (w instanceof WarHammer) {
+ n = new Glaive();
+ }
+
+ if (n != null) {
+
+ int level = w.level;
+ if (level > 0) {
+ n.upgrade( level );
+ } else if (level < 0) {
+ n.degrade( -level );
+ }
+
+ if (w.isEnchanted()) {
+ n.enchant( Enchantment.random() );
+ }
+
+ n.levelKnown = w.levelKnown;
+ n.cursedKnown = w.cursedKnown;
+ n.cursed = w.cursed;
+
+ Journal.remove( Feature.WELL_OF_TRANSMUTATION );
+
+ return n;
+ } else {
+ return null;
+ }
+ }
+
+ private Ring changeRing( Ring r ) {
+ Ring n;
+ do {
+ n = (Ring)Generator.random( Category.RING );
+ } while (n.getClass() == r.getClass());
+
+ n.level = 0;
+
+ int level = r.level;
+ if (level > 0) {
+ n.upgrade( level );
+ } else if (level < 0) {
+ n.degrade( -level );
+ }
+
+ n.levelKnown = r.levelKnown;
+ n.cursedKnown = r.cursedKnown;
+ n.cursed = r.cursed;
+
+ return n;
+ }
+
+ private Wand changeWand( Wand w ) {
+
+ Wand n;
+ do {
+ n = (Wand)Generator.random( Category.WAND );
+ } while (n.getClass() == w.getClass());
+
+ n.level = 0;
+ n.upgrade( w.level );
+
+ n.levelKnown = w.levelKnown;
+ n.cursedKnown = w.cursedKnown;
+ n.cursed = w.cursed;
+
+ return n;
+ }
+
+ private Plant.Seed changeSeed( Plant.Seed s ) {
+
+ Plant.Seed n;
+
+ do {
+ n = (Plant.Seed)Generator.random( Category.SEED );
+ } while (n.getClass() == s.getClass());
+
+ return n;
+ }
+
+ private Scroll changeScroll( Scroll s ) {
+ if (s instanceof ScrollOfUpgrade) {
+
+ return new ScrollOfWeaponUpgrade();
+
+ } else if (s instanceof ScrollOfWeaponUpgrade) {
+
+ return new ScrollOfUpgrade();
+
+ } else {
+
+ Scroll n;
+ do {
+ n = (Scroll)Generator.random( Category.SCROLL );
+ } while (n.getClass() == s.getClass());
+ return n;
+ }
+ }
+
+ private Potion changePotion( Potion p ) {
+ if (p instanceof PotionOfStrength) {
+
+ return new PotionOfMight();
+
+ } else if (p instanceof PotionOfMight) {
+
+ return new PotionOfStrength();
+
+ } else {
+
+ Potion n;
+ do {
+ n = (Potion)Generator.random( Category.POTION );
+ } while (n.getClass() == p.getClass());
+ return n;
+ }
+ }
+
+ @Override
+ public String tileDesc() {
+ return
+ "Power of change radiates from the water of this well. " +
+ "Throw an item into the well to turn it into something else.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/Web.java b/src/com/watabou/pixeldungeon/actors/blobs/Web.java
new file mode 100644
index 0000000000..00695bbf36
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/Web.java
@@ -0,0 +1,68 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.effects.BlobEmitter;
+import com.watabou.pixeldungeon.effects.particles.WebParticle;
+
+public class Web extends Blob {
+
+ @Override
+ protected void evolve() {
+
+ for (int i=0; i < LENGTH; i++) {
+
+ int offv = cur[i] > 0 ? cur[i] - 1 : 0;
+ off[i] = offv;
+
+ if (offv > 0) {
+
+ volume += offv;
+
+ Char ch = Actor.findChar( i );
+ if (ch != null) {
+ Buff.prolong( ch, Roots.class, TICK );
+ }
+ }
+ }
+ }
+
+ @Override
+ public void use( BlobEmitter emitter ) {
+ super.use( emitter );
+
+ emitter.pour( WebParticle.FACTORY, 0.4f );
+ }
+
+ public void seed( int cell, int amount ) {
+ int diff = amount - cur[cell];
+ if (diff > 0) {
+ cur[cell] = amount;
+ volume += diff;
+ }
+ }
+
+ @Override
+ public String tileDesc() {
+ return "Everything is covered with a thick web here.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/blobs/WellWater.java b/src/com/watabou/pixeldungeon/actors/blobs/WellWater.java
new file mode 100644
index 0000000000..d59956d6a8
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/blobs/WellWater.java
@@ -0,0 +1,147 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.blobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.Journal.Feature;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class WellWater extends Blob {
+
+ protected int pos;
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+
+ for (int i=0; i < LENGTH; i++) {
+ if (cur[i] > 0) {
+ pos = i;
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void evolve() {
+ volume = off[pos] = cur[pos];
+
+ if (Dungeon.visible[pos]) {
+ if (this instanceof WaterOfAwareness) {
+ Journal.add( Feature.WELL_OF_AWARENESS );
+ } else if (this instanceof WaterOfHealth) {
+ Journal.add( Feature.WELL_OF_HEALTH );
+ } else if (this instanceof WaterOfTransmutation) {
+ Journal.add( Feature.WELL_OF_TRANSMUTATION );
+ }
+ }
+ }
+
+ protected boolean affect() {
+
+ Heap heap;
+
+ if (pos == Dungeon.hero.pos && affectHero( Dungeon.hero )) {
+
+ volume = off[pos] = cur[pos] = 0;
+ return true;
+
+ } else if ((heap = Dungeon.level.heaps.get( pos )) != null) {
+
+ Item oldItem = heap.peek();
+ Item newItem = affectItem( oldItem );
+
+ if (newItem != null) {
+
+ if (newItem == oldItem) {
+
+ } else if (oldItem.quantity() > 1) {
+
+ oldItem.quantity( oldItem.quantity() - 1 );
+ heap.drop( newItem );
+
+ } else {
+ heap.replace( oldItem, newItem );
+ }
+
+ heap.sprite.link();
+ volume = off[pos] = cur[pos] = 0;
+
+ return true;
+
+ } else {
+
+ int newPlace;
+ do {
+ newPlace = pos + Level.NEIGHBOURS8[Random.Int( 8 )];
+ } while (!Level.passable[newPlace] && !Level.avoid[newPlace]);
+ Dungeon.level.drop( heap.pickUp(), newPlace ).sprite.drop( pos );
+
+ return false;
+
+ }
+
+ } else {
+
+ return false;
+
+ }
+ }
+
+ protected boolean affectHero( Hero hero ) {
+ return false;
+ }
+
+ protected Item affectItem( Item item ) {
+ return null;
+ }
+
+ @Override
+ public void seed( int cell, int amount ) {
+ cur[pos] = 0;
+ pos = cell;
+ volume = cur[pos] = amount;
+ }
+
+ public static void affectCell( int cell ) {
+
+ Class>[] waters = {WaterOfHealth.class, WaterOfAwareness.class, WaterOfTransmutation.class};
+
+ for (Class>waterClass : waters) {
+ WellWater water = (WellWater)Dungeon.level.blobs.get( waterClass );
+ if (water != null &&
+ water.volume > 0 &&
+ water.pos == cell &&
+ water.affect()) {
+
+ Level.set( cell, Terrain.EMPTY_WELL );
+ GameScene.updateMap( cell );
+
+ return;
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Amok.java b/src/com/watabou/pixeldungeon/actors/buffs/Amok.java
new file mode 100644
index 0000000000..48a260a46e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Amok.java
@@ -0,0 +1,33 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Amok extends FlavourBuff {
+
+ @Override
+ public int icon() {
+ return BuffIndicator.AMOK;
+ }
+
+ @Override
+ public String toString() {
+ return "Amok";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Awareness.java b/src/com/watabou/pixeldungeon/actors/buffs/Awareness.java
new file mode 100644
index 0000000000..252e901e6f
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Awareness.java
@@ -0,0 +1,31 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+
+public class Awareness extends FlavourBuff {
+
+ public static final float DURATION = 2f;
+
+ @Override
+ public void detach() {
+ super.detach();
+ Dungeon.observe();
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Barkskin.java b/src/com/watabou/pixeldungeon/actors/buffs/Barkskin.java
new file mode 100644
index 0000000000..e8e53ae8ee
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Barkskin.java
@@ -0,0 +1,63 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Barkskin extends Buff {
+
+ private int level = 0;
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ spend( TICK );
+ if (--level <= 0) {
+ detach();
+ }
+
+ } else {
+
+ detach();
+
+ }
+
+ return true;
+ }
+
+ public int level() {
+ return level;
+ }
+
+ public void level( int value ) {
+ if (level < value) {
+ level = value;
+ }
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.BARKSKIN;
+ }
+
+ @Override
+ public String toString() {
+ return "Barkskin";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Bleeding.java b/src/com/watabou/pixeldungeon/actors/buffs/Bleeding.java
new file mode 100644
index 0000000000..32f8d9f07d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Bleeding.java
@@ -0,0 +1,93 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.effects.Splash;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class Bleeding extends Buff {
+
+ protected int level;
+
+ private static final String LEVEL = "level";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( LEVEL, level );
+
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ level = bundle.getInt( LEVEL );
+ }
+
+ public void set( int level ) {
+ this.level = level;
+ };
+
+ @Override
+ public int icon() {
+ return BuffIndicator.BLEEDING;
+ }
+
+ @Override
+ public String toString() {
+ return "Bleeding";
+ }
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ if ((level = Random.Int( level / 2, level )) > 0) {
+
+ target.damage( level, this );
+ if (target.sprite.visible) {
+ Splash.at( target.sprite.center(), -PointF.PI / 2, PointF.PI / 6,
+ target.sprite.blood(), Math.min( 10 * level / target.HT, 10 ) );
+ }
+
+ if (target == Dungeon.hero && !target.isAlive()) {
+ Dungeon.fail( Utils.format( ResultDescriptions.BLEEDING, Dungeon.depth ) );
+ GLog.n( "You bled to death..." );
+ }
+
+ spend( TICK );
+ } else {
+ detach();
+ }
+
+ } else {
+
+ detach();
+
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Blindness.java b/src/com/watabou/pixeldungeon/actors/buffs/Blindness.java
new file mode 100644
index 0000000000..4ef36a9ff0
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Blindness.java
@@ -0,0 +1,40 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Blindness extends FlavourBuff {
+
+ @Override
+ public void detach() {
+ super.detach();
+ Dungeon.observe();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.BLINDNESS;
+ }
+
+ @Override
+ public String toString() {
+ return "Blinded";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Buff.java b/src/com/watabou/pixeldungeon/actors/buffs/Buff.java
new file mode 100644
index 0000000000..0b5aa1632e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Buff.java
@@ -0,0 +1,90 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Buff extends Actor {
+
+ public Char target;
+
+ public boolean attachTo( Char target ) {
+
+ if (target.immunities().contains( getClass() )) {
+ return false;
+ }
+
+ this.target = target;
+ target.add( this );
+
+ return true;
+ }
+
+ public void detach() {
+ target.remove( this );
+ }
+
+ @Override
+ public boolean act() {
+ diactivate();
+ return true;
+ }
+
+ public int icon() {
+ return BuffIndicator.NONE;
+ }
+
+ public static T affect( Char target, Class buffClass ) {
+ T buff = target.buff( buffClass );
+ if (buff != null) {
+ return buff;
+ } else {
+ try {
+ buff = buffClass.newInstance();
+ buff.attachTo( target );
+ return buff;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ }
+
+ public static T affect( Char target, Class buffClass, float duration ) {
+ T buff = affect( target, buffClass );
+ buff.spend( duration );
+ return buff;
+ }
+
+ public static T prolong( Char target, Class buffClass, float duration ) {
+ T buff = affect( target, buffClass );
+ buff.postpone( duration );
+ return buff;
+ }
+
+ public static void detach( Buff buff ) {
+ if (buff != null) {
+ buff.detach();
+ }
+ }
+
+ public static void detach( Char target, Class extends Buff> cl ) {
+ detach( target.buff( cl ) );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Burning.java b/src/com/watabou/pixeldungeon/actors/buffs/Burning.java
new file mode 100644
index 0000000000..e88687b2bd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Burning.java
@@ -0,0 +1,154 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.Fire;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.Thief;
+import com.watabou.pixeldungeon.effects.particles.ElmoParticle;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.food.ChargrilledMeat;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.items.scrolls.Scroll;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Burning extends Buff implements Hero.Doom {
+
+ private static final String TXT_BURNS_UP = "%s burns up!";
+ private static final String TXT_BURNED_TO_DEATH = "You burned to death...";
+
+ private static final float DURATION = 8f;
+
+ private float left;
+
+ private static final String LEFT = "left";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( LEFT, left );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle(bundle);
+ left = bundle.getFloat( LEFT );
+ }
+
+ @Override
+ public boolean act() {
+
+ if (target.isAlive()) {
+
+ if (target instanceof Hero) {
+ Buff.prolong( target, Light.class, TICK * 1.01f );
+ }
+
+ target.damage( Random.Int( 1, 5 ), this );
+
+ if (target instanceof Hero) {
+
+ Item item = ((Hero)target).belongings.randomUnequipped();
+ if (item instanceof Scroll) {
+
+ item = item.detach( ((Hero)target).belongings.backpack );
+ GLog.w( TXT_BURNS_UP, item.toString() );
+
+ Heap.burnFX( target.pos );
+
+ } else if (item instanceof MysteryMeat) {
+
+ item = item.detach( ((Hero)target).belongings.backpack );
+ ChargrilledMeat steak = new ChargrilledMeat();
+ if (!steak.collect( ((Hero)target).belongings.backpack )) {
+ Dungeon.level.drop( steak, target.pos ).sprite.drop();
+ }
+ GLog.w( TXT_BURNS_UP, item.toString() );
+
+ Heap.burnFX( target.pos );
+
+ }
+
+ } else if (target instanceof Thief && ((Thief)target).item instanceof Scroll) {
+
+ ((Thief)target).item = null;
+ target.sprite.emitter().burst( ElmoParticle.FACTORY, 6 );
+ }
+
+ } else {
+ detach();
+ }
+
+ if (Level.flamable[target.pos]) {
+ GameScene.add( Blob.seed( target.pos, 4, Fire.class ) );
+ }
+
+ spend( TICK );
+ left -= TICK;
+
+ if (left <= 0 ||
+ Random.Float() > (2 + (float)target.HP / target.HT) / 3 ||
+ (Level.water[target.pos] && !target.flying)) {
+
+ detach();
+ }
+
+ return true;
+ }
+
+ public void reignite( Char ch ) {
+ left = duration( ch );
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.FIRE;
+ }
+
+ @Override
+ public String toString() {
+ return "Burning";
+ }
+
+ public static float duration( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() * DURATION : DURATION;
+ }
+
+ @Override
+ public void onDeath() {
+
+ Badges.validateDeathFromFire();
+
+ Dungeon.fail( Utils.format( ResultDescriptions.BURNING, Dungeon.depth ) );
+ GLog.n( TXT_BURNED_TO_DEATH );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Charm.java b/src/com/watabou/pixeldungeon/actors/buffs/Charm.java
new file mode 100644
index 0000000000..d363accfc1
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Charm.java
@@ -0,0 +1,56 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Charm extends FlavourBuff {
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ target.pacified = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.pacified = false;
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.HEART;
+ }
+
+ @Override
+ public String toString() {
+ return "Charmed";
+ }
+
+ public static float durationFactor( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() : 1;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Combo.java b/src/com/watabou/pixeldungeon/actors/buffs/Combo.java
new file mode 100644
index 0000000000..9956f47c85
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Combo.java
@@ -0,0 +1,67 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+
+public class Combo extends Buff {
+
+ private static String TXT_COMBO = "%d hit combo!";
+
+ public int count = 0;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.COMBO;
+ }
+
+ @Override
+ public String toString() {
+ return "Combo";
+ }
+
+ public int hit( Char enemy, int damage ) {
+
+ count++;
+
+ if (count >= 3) {
+
+ Badges.validateMasteryCombo( count );
+
+ GLog.p( TXT_COMBO, count );
+ postpone( 1.41f - count / 10f );
+ return (int)(damage * (count - 2) / 5f);
+
+ } else {
+
+ postpone( 1.1f );
+ return 0;
+
+ }
+ }
+
+ @Override
+ public boolean act() {
+ detach();
+ return true;
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Cripple.java b/src/com/watabou/pixeldungeon/actors/buffs/Cripple.java
new file mode 100644
index 0000000000..c50c3b418d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Cripple.java
@@ -0,0 +1,35 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Cripple extends FlavourBuff {
+
+ public static final float DURATION = 10f;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.CRIPPLE;
+ }
+
+ @Override
+ public String toString() {
+ return "Crippled";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/FlavourBuff.java b/src/com/watabou/pixeldungeon/actors/buffs/FlavourBuff.java
new file mode 100644
index 0000000000..01cb7ffe59
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/FlavourBuff.java
@@ -0,0 +1,28 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+// Special kind of buff, that doesn't perform any kind actions
+public class FlavourBuff extends Buff {
+
+ @Override
+ public boolean act() {
+ detach();
+ return true;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Frost.java b/src/com/watabou/pixeldungeon/actors/buffs/Frost.java
new file mode 100644
index 0000000000..18546ce811
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Frost.java
@@ -0,0 +1,79 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.food.FrozenCarpaccio;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Frost extends FlavourBuff {
+
+ private static final float DURATION = 5f;
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+
+ target.paralysed = true;
+ Burning.detach( target, Burning.class );
+
+ if (target instanceof Hero) {
+ Hero hero = (Hero)target;
+ Item item = hero.belongings.randomUnequipped();
+ if (item instanceof MysteryMeat) {
+
+ item = item.detach( hero.belongings.backpack );
+ FrozenCarpaccio carpaccio = new FrozenCarpaccio();
+ if (!carpaccio.collect( hero.belongings.backpack )) {
+ Dungeon.level.drop( carpaccio, target.pos ).sprite.drop();
+ }
+ }
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.paralysed = false;
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.FROST;
+ }
+
+ @Override
+ public String toString() {
+ return "Frozen";
+ }
+
+ public static float duration( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() * DURATION : DURATION;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Fury.java b/src/com/watabou/pixeldungeon/actors/buffs/Fury.java
new file mode 100644
index 0000000000..fb2777a471
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Fury.java
@@ -0,0 +1,46 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Fury extends Buff {
+
+ public static float LEVEL = 0.4f;
+
+ @Override
+ public boolean act() {
+ if (target.HP > target.HT * LEVEL) {
+ detach();
+ }
+
+ spend( TICK );
+
+ return true;
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.FURY;
+ }
+
+ @Override
+ public String toString() {
+ return "Fury";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/GasesImmunity.java b/src/com/watabou/pixeldungeon/actors/buffs/GasesImmunity.java
new file mode 100644
index 0000000000..fab1d89fb9
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/GasesImmunity.java
@@ -0,0 +1,44 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class GasesImmunity extends FlavourBuff {
+
+ public static final float DURATION = 5f;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.IMMUNITY;
+ }
+
+ @Override
+ public String toString() {
+ return "Immune to gases";
+ }
+
+ public static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Paralysis.class );
+ IMMUNITIES.add( ToxicGas.class );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Hunger.java b/src/com/watabou/pixeldungeon/actors/buffs/Hunger.java
new file mode 100644
index 0000000000..4e4ea63c88
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Hunger.java
@@ -0,0 +1,159 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.items.rings.RingOfSatiety;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Hunger extends Buff implements Hero.Doom {
+
+ private static final float STEP = 10f;
+
+ public static final float HUNGRY = 260f;
+ public static final float STARVING = 360f;
+
+ private static final String TXT_HUNGRY = "You are hungry.";
+ private static final String TXT_STARVING = "You are starving!";
+ private static final String TXT_DEATH = "You starved to death...";
+
+ private float level;
+
+ private static final String LEVEL = "level";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( LEVEL, level );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ level = bundle.getFloat( LEVEL );
+ }
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ Hero hero = (Hero)target;
+
+ if (isStarving()) {
+
+ if (Random.Float() < 0.3f && (target.HP > 1 || !target.paralysed)) {
+
+ GLog.n( TXT_STARVING );
+ hero.damage( 1, this );
+
+ hero.interrupt();
+ }
+ } else {
+
+ int bonus = 0;
+ for (Buff buff : target.buffs( RingOfSatiety.Satiety.class )) {
+ bonus += ((RingOfSatiety.Satiety)buff).level;
+ }
+
+ float newLevel = level + STEP - bonus;
+ boolean statusUpdated = false;
+ if (newLevel >= STARVING) {
+
+ GLog.n( TXT_STARVING );
+ statusUpdated = true;
+
+ hero.interrupt();
+
+ } else if (newLevel >= HUNGRY && level < HUNGRY) {
+
+ GLog.w( TXT_HUNGRY );
+ statusUpdated = true;
+
+ }
+ level = newLevel;
+
+ if (statusUpdated) {
+ BuffIndicator.refreshHero();
+ }
+
+ }
+
+ float step = ((Hero)target).heroClass == HeroClass.ROGUE ? STEP * 1.2f : STEP;
+ spend( target.buff( Shadows.class ) == null ? step : step * 1.5f );
+
+ } else {
+
+ diactivate();
+
+ }
+
+ return true;
+ }
+
+ public void satisfy( float energy ) {
+ level -= energy;
+ if (level < 0) {
+ level = 0;
+ } else if (level > STARVING) {
+ level = STARVING;
+ }
+
+ BuffIndicator.refreshHero();
+ }
+
+ public boolean isStarving() {
+ return level >= STARVING;
+ }
+
+ @Override
+ public int icon() {
+ if (level < HUNGRY) {
+ return BuffIndicator.NONE;
+ } else if (level < STARVING) {
+ return BuffIndicator.HUNGER;
+ } else {
+ return BuffIndicator.STARVATION;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (level < STARVING) {
+ return "Hungry";
+ } else {
+ return "Starving";
+ }
+ }
+
+ @Override
+ public void onDeath() {
+
+ Badges.validateDeathFromHunger();
+
+ Dungeon.fail( Utils.format( ResultDescriptions.HUNGER, Dungeon.depth ) );
+ GLog.n( TXT_DEATH );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Invisibility.java b/src/com/watabou/pixeldungeon/actors/buffs/Invisibility.java
new file mode 100644
index 0000000000..cf44544e3c
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Invisibility.java
@@ -0,0 +1,60 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Invisibility extends FlavourBuff {
+
+ public static final float DURATION = 15f;
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ target.invisible++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.invisible--;
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.INVISIBLE;
+ }
+
+ @Override
+ public String toString() {
+ return "Invisible";
+ }
+
+ public static void dispel() {
+ Invisibility buff = Dungeon.hero.buff( Invisibility.class );
+ if (buff != null && Dungeon.hero.visibleEnemies() > 0) {
+ buff.detach();
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Levitation.java b/src/com/watabou/pixeldungeon/actors/buffs/Levitation.java
new file mode 100644
index 0000000000..c3d64c850e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Levitation.java
@@ -0,0 +1,55 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Levitation extends FlavourBuff {
+
+ public static final float DURATION = 20f;
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ target.flying = true;
+ Roots.detach( target, Roots.class );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.flying = false;
+ Dungeon.level.press( target.pos, target );
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.LEVITATION;
+ }
+
+ @Override
+ public String toString() {
+ return "Levitating";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Light.java b/src/com/watabou/pixeldungeon/actors/buffs/Light.java
new file mode 100644
index 0000000000..7f2baa4381
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Light.java
@@ -0,0 +1,59 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Light extends FlavourBuff {
+
+ public static final float DURATION = 250f;
+ public static final int DISTANCE = 4;
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ // When a level is loading, do nothing
+ if (Dungeon.level != null) {
+ target.viewDistance = Math.max( Dungeon.level.viewDistance, DISTANCE );
+ Dungeon.observe();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.viewDistance = Dungeon.level.viewDistance;
+ Dungeon.observe();
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.LIGHT;
+ }
+
+ @Override
+ public String toString() {
+ return "Illuminated";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/MindVision.java b/src/com/watabou/pixeldungeon/actors/buffs/MindVision.java
new file mode 100644
index 0000000000..4032e60ef6
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/MindVision.java
@@ -0,0 +1,44 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class MindVision extends FlavourBuff {
+
+ public static final float DURATION = 20f;
+
+ public int distance = 2;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.MIND_VISION;
+ }
+
+ @Override
+ public String toString() {
+ return "Mind vision";
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ Dungeon.observe();
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Ooze.java b/src/com/watabou/pixeldungeon/actors/buffs/Ooze.java
new file mode 100644
index 0000000000..564e46d4dd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Ooze.java
@@ -0,0 +1,58 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+
+public class Ooze extends Buff {
+
+ private static final String TXT_HERO_KILLED = "%s killed you...";
+
+ public int damage = 1;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.OOZE;
+ }
+
+ @Override
+ public String toString() {
+ return "Caustic ooze";
+ }
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+ target.damage( damage, this );
+ if (!target.isAlive() && target == Dungeon.hero) {
+ Dungeon.fail( Utils.format( ResultDescriptions.OOZE, Dungeon.depth ) );
+ GLog.n( TXT_HERO_KILLED, toString() );
+ }
+ spend( TICK );
+ }
+ if (Level.water[target.pos]) {
+ detach();
+ }
+ return true;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Paralysis.java b/src/com/watabou/pixeldungeon/actors/buffs/Paralysis.java
new file mode 100644
index 0000000000..4623951157
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Paralysis.java
@@ -0,0 +1,58 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Paralysis extends FlavourBuff {
+
+ private static final float DURATION = 10f;
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ target.paralysed = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.paralysed = false;
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.PARALYSIS;
+ }
+
+ @Override
+ public String toString() {
+ return "Paralysed";
+ }
+
+ public static float duration( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() * DURATION : DURATION;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Poison.java b/src/com/watabou/pixeldungeon/actors/buffs/Poison.java
new file mode 100644
index 0000000000..fe223d5395
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Poison.java
@@ -0,0 +1,98 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+
+public class Poison extends Buff implements Hero.Doom {
+
+ public static final int DOT = 2;
+
+ protected float left;
+
+ private static final String LEFT = "left";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( LEFT, left );
+
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ left = bundle.getFloat( LEFT );
+ }
+
+ public void set( float duration ) {
+ this.left = duration;
+ };
+
+ @Override
+ public int icon() {
+ return BuffIndicator.POISON;
+ }
+
+ @Override
+ public String toString() {
+ return "Poisoned";
+ }
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ target.damage( DOT, this );
+ spend( TICK );
+
+ if ((left -= TICK) <= 0) {
+ detach();
+ }
+
+ } else {
+
+ detach();
+
+ }
+
+ return true;
+ }
+
+ public static float durationFactor( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() : 1;
+ }
+
+ @Override
+ public void onDeath() {
+ Badges.validateDeathFromPoison();
+
+ Dungeon.fail( Utils.format( ResultDescriptions.POISON, Dungeon.depth ) );
+ GLog.n( "You died from poison..." );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Regeneration.java b/src/com/watabou/pixeldungeon/actors/buffs/Regeneration.java
new file mode 100644
index 0000000000..790dbdc7f5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Regeneration.java
@@ -0,0 +1,50 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.rings.RingOfMending;
+
+public class Regeneration extends Buff {
+
+ private static final float REGENERATION_DELAY = 10;
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ if (target.HP < target.HT && !((Hero)target).isStarving()) {
+ target.HP += 1;
+ }
+
+ int bonus = 0;
+ for (Buff buff : target.buffs( RingOfMending.Rejuvenation.class )) {
+ bonus += ((RingOfMending.Rejuvenation)buff).level;
+ }
+
+ spend( (float)(REGENERATION_DELAY / Math.pow( 1.2, bonus )) );
+
+ } else {
+
+ diactivate();
+
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Roots.java b/src/com/watabou/pixeldungeon/actors/buffs/Roots.java
new file mode 100644
index 0000000000..932dfbbcea
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Roots.java
@@ -0,0 +1,50 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Roots extends FlavourBuff {
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (!target.flying && super.attachTo( target )) {
+ target.rooted = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ target.rooted = false;
+ super.detach();
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.ROOTS;
+ }
+
+ @Override
+ public String toString() {
+ return "Rooted";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Shadows.java b/src/com/watabou/pixeldungeon/actors/buffs/Shadows.java
new file mode 100644
index 0000000000..ae6e4fc928
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Shadows.java
@@ -0,0 +1,96 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.utils.Bundle;
+
+public class Shadows extends Invisibility {
+
+ protected float left;
+
+ private static final String LEFT = "left";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( LEFT, left );
+
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ left = bundle.getFloat( LEFT );
+ }
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ Sample.INSTANCE.play( Assets.SND_MELD );
+ Dungeon.observe();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ Dungeon.observe();
+ }
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ spend( TICK * 2 );
+
+ if (--left <= 0 || Dungeon.hero.visibleEnemies() > 0) {
+ detach();
+ }
+
+ } else {
+
+ detach();
+
+ }
+
+ return true;
+ }
+
+ public void prolong() {
+ left = 2;
+ }
+
+ @Override
+ public int icon() {
+ return BuffIndicator.SHADOWS;
+ }
+
+ @Override
+ public String toString() {
+ return "Shadowmelded";
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Sleep.java b/src/com/watabou/pixeldungeon/actors/buffs/Sleep.java
new file mode 100644
index 0000000000..7ffff90e13
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Sleep.java
@@ -0,0 +1,24 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+public class Sleep extends FlavourBuff {
+
+ public static final float SWS = 1.5f;
+
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Slow.java b/src/com/watabou/pixeldungeon/actors/buffs/Slow.java
new file mode 100644
index 0000000000..34f55234fb
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Slow.java
@@ -0,0 +1,42 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Slow extends FlavourBuff {
+
+ private static final float DURATION = 10f;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.SLOW;
+ }
+
+ @Override
+ public String toString() {
+ return "Slowed";
+ }
+
+ public static float duration( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() * DURATION : DURATION;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/SnipersMark.java b/src/com/watabou/pixeldungeon/actors/buffs/SnipersMark.java
new file mode 100644
index 0000000000..01ca64f758
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/SnipersMark.java
@@ -0,0 +1,33 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class SnipersMark extends FlavourBuff {
+
+ @Override
+ public int icon() {
+ return BuffIndicator.MARK;
+ }
+
+ @Override
+ public String toString() {
+ return "Sniper's mark";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Speed.java b/src/com/watabou/pixeldungeon/actors/buffs/Speed.java
new file mode 100644
index 0000000000..cccdc6950c
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Speed.java
@@ -0,0 +1,24 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+public class Speed extends FlavourBuff {
+
+ public static final float DURATION = 10f;
+
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Terror.java b/src/com/watabou/pixeldungeon/actors/buffs/Terror.java
new file mode 100644
index 0000000000..d905e2d81d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Terror.java
@@ -0,0 +1,53 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.utils.Bundle;
+
+public class Terror extends FlavourBuff {
+
+ public static final float DURATION = 10f;
+ public Char source;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.TERROR;
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ // It's not really correct...
+ source = Dungeon.hero;
+ }
+
+ @Override
+ public String toString() {
+ return "Terror";
+ }
+
+ public static void recover( Char target ) {
+ Terror terror = target.buff( Terror.class );
+ if (terror != null && terror.cooldown() < DURATION) {
+ target.remove( terror );
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/buffs/Weakness.java b/src/com/watabou/pixeldungeon/actors/buffs/Weakness.java
new file mode 100644
index 0000000000..52df8f84b8
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/buffs/Weakness.java
@@ -0,0 +1,62 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.buffs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.rings.RingOfElements.Resistance;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+
+public class Weakness extends FlavourBuff {
+
+ private static final float DURATION = 40f;
+
+ @Override
+ public int icon() {
+ return BuffIndicator.WEAKNESS;
+ }
+
+ @Override
+ public String toString() {
+ return "Weakened";
+ }
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ Hero hero = (Hero)target;
+ hero.weakened = true;
+ hero.belongings.discharge();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ ((Hero)target).weakened = false;
+ }
+
+ public static float duration( Char ch ) {
+ Resistance r = ch.buff( Resistance.class );
+ return r != null ? r.durationFactor() * DURATION : DURATION;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/hero/Belongings.java b/src/com/watabou/pixeldungeon/actors/hero/Belongings.java
new file mode 100644
index 0000000000..88a594b338
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/hero/Belongings.java
@@ -0,0 +1,299 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.hero;
+
+import java.util.Iterator;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.KindOfWeapon;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.bags.Bag;
+import com.watabou.pixeldungeon.items.keys.IronKey;
+import com.watabou.pixeldungeon.items.keys.Key;
+import com.watabou.pixeldungeon.items.rings.Ring;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfRemoveCurse;
+import com.watabou.pixeldungeon.items.wands.Wand;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Belongings implements Iterable- {
+
+ public static final int BACKPACK_SIZE = 19;
+
+ private Hero owner;
+
+ public Bag backpack;
+
+ public KindOfWeapon weapon = null;
+ public Armor armor = null;
+ public Ring ring1 = null;
+ public Ring ring2 = null;
+
+ public Belongings( Hero owner ) {
+ this.owner = owner;
+
+ backpack = new Bag() {{
+ name = "backpack";
+ size = BACKPACK_SIZE;
+ }};
+ backpack.owner = owner;
+ }
+
+ private static final String WEAPON = "weapon";
+ private static final String ARMOR = "armor";
+ private static final String RING1 = "ring1";
+ private static final String RING2 = "ring2";
+
+ public void storeInBundle( Bundle bundle ) {
+
+ backpack.storeInBundle( bundle );
+
+ bundle.put( WEAPON, weapon );
+ bundle.put( ARMOR, armor );
+ bundle.put( RING1, ring1 );
+ bundle.put( RING2, ring2 );
+ }
+
+ public void restoreFromBundle( Bundle bundle ) {
+
+ backpack.clear();
+ backpack.restoreFromBundle( bundle );
+
+ weapon = (KindOfWeapon)bundle.get( WEAPON );
+ if (weapon != null) {
+ weapon.activate( owner );
+ }
+
+ armor = (Armor)bundle.get( ARMOR );
+
+ ring1 = (Ring)bundle.get( RING1 );
+ if (ring1 != null) {
+ ring1.activate( owner );
+ }
+
+ ring2 = (Ring)bundle.get( RING2 );
+ if (ring2 != null) {
+ ring2.activate( owner );
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getItem( Class itemClass ) {
+
+ for (Item item : this) {
+ if (itemClass.isInstance( item )) {
+ return (T)item;
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getKey( Class kind, int depth ) {
+
+ for (Item item : backpack) {
+ if (item.getClass() == kind && ((Key)item).depth == depth) {
+ return (T)item;
+ }
+ }
+
+ return null;
+ }
+
+ public void countIronKeys() {
+
+ IronKey.curDepthQunatity = 0;
+
+ for (Item item : backpack) {
+ if (item instanceof IronKey && ((IronKey)item).depth == Dungeon.depth) {
+ IronKey.curDepthQunatity = item.quantity();
+ return;
+ }
+ }
+ }
+
+ public void identify() {
+ for (Item item : this) {
+ item.identify();
+ }
+ }
+
+ public void observe() {
+ if (weapon != null) {
+ weapon.identify();
+ Badges.validateItemLevelAquired( weapon );
+ }
+ if (armor != null) {
+ armor.identify();
+ Badges.validateItemLevelAquired( armor );
+ }
+ if (ring1 != null) {
+ ring1.identify();
+ Badges.validateItemLevelAquired( ring1 );
+ }
+ if (ring2 != null) {
+ ring2.identify();
+ Badges.validateItemLevelAquired( ring2 );
+ }
+ for (Item item : backpack) {
+ item.cursedKnown = true;
+ }
+ }
+
+ public void uncurseEquipped() {
+ ScrollOfRemoveCurse.uncurse( owner, armor, weapon, ring1, ring2 );
+ }
+
+ public Item randomUnequipped() {
+ return Random.element( backpack.items );
+ }
+
+ public void resurrect( int depth ) {
+
+ for (Item item : backpack.items.toArray( new Item[0])) {
+ if (item instanceof Key) {
+ if (((Key)item).depth == depth) {
+ item.detachAll( backpack );
+ }
+ } else if (item.unique) {
+
+ } else if (!item.isEquipped( owner )) {
+ item.detachAll( backpack );
+ }
+ }
+
+ if (weapon != null) {
+ weapon.cursed = false;
+ weapon.activate( owner );
+ }
+
+ if (armor != null) {
+ armor.cursed = false;
+ }
+
+ if (ring1 != null) {
+ ring1.cursed = false;
+ ring1.activate( owner );
+ }
+ if (ring2 != null) {
+ ring2.cursed = false;
+ ring2.activate( owner );
+ }
+ }
+
+ public int charge( boolean full) {
+
+ int count = 0;
+
+ for (Item item : this) {
+ if (item instanceof Wand) {
+ Wand wand = (Wand)item;
+ if (wand.curCharges < wand.maxCharges) {
+ wand.curCharges = full ? wand.maxCharges : wand.curCharges + 1;
+ count++;
+
+ wand.updateQuickslot();
+ }
+ }
+ }
+
+ return count;
+ }
+
+ public int discharge() {
+
+ int count = 0;
+
+ for (Item item : this) {
+ if (item instanceof Wand) {
+ Wand wand = (Wand)item;
+ if (wand.curCharges > 0) {
+ wand.curCharges--;
+ count++;
+
+ wand.updateQuickslot();
+ }
+ }
+ }
+
+ return count;
+ }
+
+ @Override
+ public Iterator
- iterator() {
+ return new ItemIterator();
+ }
+
+ private class ItemIterator implements Iterator
- {
+
+ private int index = 0;
+
+ private Iterator
- backpackIterator = backpack.iterator();
+
+ private Item[] equipped = {weapon, armor, ring1, ring2};
+ private int backpackIndex = equipped.length;
+
+ @Override
+ public boolean hasNext() {
+
+ for (int i=index; i < backpackIndex; i++) {
+ if (equipped[i] != null) {
+ return true;
+ }
+ }
+
+ return backpackIterator.hasNext();
+ }
+
+ @Override
+ public Item next() {
+
+ while (index < backpackIndex) {
+ Item item = equipped[index++];
+ if (item != null) {
+ return item;
+ }
+ }
+
+ return backpackIterator.next();
+ }
+
+ @Override
+ public void remove() {
+ switch (index) {
+ case 0:
+ equipped[0] = weapon = null;
+ break;
+ case 1:
+ equipped[1] = armor = null;
+ break;
+ case 2:
+ equipped[2] = ring1 = null;
+ break;
+ case 3:
+ equipped[3] = ring2 = null;
+ break;
+ default:
+ backpackIterator.remove();
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/hero/Hero.java b/src/com/watabou/pixeldungeon/actors/hero/Hero.java
new file mode 100644
index 0000000000..daf8d57404
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/hero/Hero.java
@@ -0,0 +1,1334 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.hero;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import com.watabou.noosa.Camera;
+import com.watabou.noosa.Game;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Bones;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.GamesInProgress;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Barkskin;
+import com.watabou.pixeldungeon.actors.buffs.Bleeding;
+import com.watabou.pixeldungeon.actors.buffs.Blindness;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Combo;
+import com.watabou.pixeldungeon.actors.buffs.Cripple;
+import com.watabou.pixeldungeon.actors.buffs.Fury;
+import com.watabou.pixeldungeon.actors.buffs.GasesImmunity;
+import com.watabou.pixeldungeon.actors.buffs.Hunger;
+import com.watabou.pixeldungeon.actors.buffs.Invisibility;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.buffs.Ooze;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.actors.buffs.Regeneration;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.actors.buffs.Charm;
+import com.watabou.pixeldungeon.actors.buffs.SnipersMark;
+import com.watabou.pixeldungeon.actors.buffs.Weakness;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.effects.CheckedCell;
+import com.watabou.pixeldungeon.effects.Flare;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.Amulet;
+import com.watabou.pixeldungeon.items.Ankh;
+import com.watabou.pixeldungeon.items.DewVial;
+import com.watabou.pixeldungeon.items.Dewdrop;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Heap.Type;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.KindOfWeapon;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.keys.GoldenKey;
+import com.watabou.pixeldungeon.items.keys.Key;
+import com.watabou.pixeldungeon.items.keys.SkeletonKey;
+import com.watabou.pixeldungeon.items.keys.IronKey;
+import com.watabou.pixeldungeon.items.potions.PotionOfStrength;
+import com.watabou.pixeldungeon.items.rings.RingOfAccuracy;
+import com.watabou.pixeldungeon.items.rings.RingOfDetection;
+import com.watabou.pixeldungeon.items.rings.RingOfElements;
+import com.watabou.pixeldungeon.items.rings.RingOfEvasion;
+import com.watabou.pixeldungeon.items.rings.RingOfHaste;
+import com.watabou.pixeldungeon.items.rings.RingOfShadows;
+import com.watabou.pixeldungeon.items.rings.RingOfThorns;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfMagicMapping;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfRecharging;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfUpgrade;
+import com.watabou.pixeldungeon.items.wands.Wand;
+import com.watabou.pixeldungeon.items.weapon.Weapon;
+import com.watabou.pixeldungeon.items.weapon.melee.MeleeWeapon;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.levels.features.AlchemyPot;
+import com.watabou.pixeldungeon.levels.features.Chasm;
+import com.watabou.pixeldungeon.plants.Earthroot;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.scenes.InterlevelScene;
+import com.watabou.pixeldungeon.scenes.SurfaceScene;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.HeroSprite;
+import com.watabou.pixeldungeon.ui.AttackIndicator;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.ui.QuickSlot;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.windows.WndMessage;
+import com.watabou.pixeldungeon.windows.WndResurrect;
+import com.watabou.pixeldungeon.windows.WndTradeItem;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Hero extends Char {
+
+ private static final String TXT_LEAVE = "One does not simply leave Pixel Dungeon.";
+
+ private static final String TXT_LEVEL_UP = "level up!";
+ private static final String TXT_NEW_LEVEL =
+ "Welcome to level %d! Now you are healthier and more focused. " +
+ "It's easier for you to hit enemies and dodge their attacks.";
+
+ public static final String TXT_YOU_NOW_HAVE = "You now have %s";
+
+ private static final String TXT_SOMETHING_ELSE = "There is something else here";
+ private static final String TXT_LOCKED_CHEST = "This chest is locked and you don't have matching key";
+ private static final String TXT_LOCKED_DOOR = "You don't have a matching key";
+ private static final String TXT_NOTICED_SMTH = "You noticed something";
+
+ private static final String TXT_WAIT = "...";
+ private static final String TXT_SEARCH = "search";
+
+ public static final int STARTING_STR = 10;
+
+ private static final float TIME_TO_REST = 1f;
+ private static final float TIME_TO_SEARCH = 2f;
+
+ public HeroClass heroClass = HeroClass.ROGUE;
+ public HeroSubClass subClass = HeroSubClass.NONE;
+
+ private int attackSkill = 10;
+ private int defenseSkill = 5;
+
+ public boolean ready = false;
+ public HeroAction curAction = null;
+ public HeroAction lastAction = null;
+
+ private Char enemy;
+
+ public Armor.Glyph killerGlyph = null;
+
+ private Item theKey;
+
+ public boolean restoreHealth = false;
+
+ public boolean usingRanged = false;
+ public Belongings belongings;
+
+ public int STR;
+ public boolean weakened = false;
+
+ public float awareness;
+
+ public int lvl = 1;
+ public int exp = 0;
+
+ private ArrayList visibleEnemies;
+
+ public Hero() {
+ super();
+ name = "you";
+
+ HP = HT = 20;
+ STR = STARTING_STR;
+ awareness = 0.1f;
+
+ belongings = new Belongings( this );
+
+ visibleEnemies = new ArrayList();
+ }
+
+ public int STR() {
+ return weakened ? STR - 2 : STR;
+ }
+
+ private static final String ATTACK = "attackSkill";
+ private static final String DEFENSE = "defenseSkill";
+ private static final String STRENGTH = "STR";
+ private static final String LEVEL = "lvl";
+ private static final String EXPERIENCE = "exp";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+
+ heroClass.storeInBundle( bundle );
+ subClass.storeInBundle( bundle );
+
+ bundle.put( ATTACK, attackSkill );
+ bundle.put( DEFENSE, defenseSkill );
+
+ bundle.put( STRENGTH, STR );
+
+ bundle.put( LEVEL, lvl );
+ bundle.put( EXPERIENCE, exp );
+
+ belongings.storeInBundle( bundle );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+
+ heroClass = HeroClass.restoreInBundle( bundle );
+ subClass = HeroSubClass.restoreInBundle( bundle );
+
+ attackSkill = bundle.getInt( ATTACK );
+ defenseSkill = bundle.getInt( DEFENSE );
+
+ STR = bundle.getInt( STRENGTH );
+ updateAwareness();
+
+ lvl = bundle.getInt( LEVEL );
+ exp = bundle.getInt( EXPERIENCE );
+
+ belongings.restoreFromBundle( bundle );
+ }
+
+ public static void preview( GamesInProgress.Info info, Bundle bundle ) {
+ // Refactoring needed!
+ Armor armor = (Armor)bundle.get( "armor" );
+ info.armor = armor == null ? 0 : armor.tier;
+ info.level = bundle.getInt( LEVEL );
+ }
+
+ public String className() {
+ return subClass == null || subClass == HeroSubClass.NONE ? heroClass.title() : subClass.title();
+ }
+
+ public void live() {
+ Buff.affect( this, Regeneration.class );
+ Buff.affect( this, Hunger.class );
+ }
+
+ public int tier() {
+ return belongings.armor == null ? 0 : belongings.armor.tier;
+ }
+
+ public boolean shoot( Char enemy, Weapon wep ) {
+
+ // Ugly...
+ usingRanged = true;
+
+ KindOfWeapon curWep = belongings.weapon;
+ belongings.weapon = wep;
+
+ boolean result = attack( enemy );
+
+ belongings.weapon = curWep;
+ usingRanged = false;
+
+ return result;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+
+ int bonus = 0;
+ for (Buff buff : buffs( RingOfAccuracy.Accuracy.class )) {
+ bonus += ((RingOfAccuracy.Accuracy)buff).level;
+ }
+ float accuracy = (bonus == 0) ? 1 : (float)Math.pow( 1.4, bonus );
+ if (usingRanged && Level.distance( pos, target.pos ) == 1) {
+ accuracy *= 0.5f;
+ }
+
+ if (belongings.weapon != null) {
+ return (int)(attackSkill * accuracy * belongings.weapon.acuracyFactor( this ));
+ } else {
+ return (int)(attackSkill * accuracy);
+ }
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+
+ int bonus = 0;
+ for (Buff buff : buffs( RingOfEvasion.Evasion.class )) {
+ bonus += ((RingOfEvasion.Evasion)buff).level;
+ }
+ float evasion = bonus == 0 ? 1 : (float)Math.pow( 1.2, bonus );
+ if (paralysed) {
+ evasion /= 2;
+ }
+
+ int aEnc = belongings.armor != null ? belongings.armor.STR - STR() : 0;
+
+ if (aEnc > 0) {
+ return (int)(defenseSkill * evasion / Math.pow( 1.5, aEnc ));
+ } else {
+
+ if (heroClass == HeroClass.ROGUE) {
+
+ if (curAction != null && subClass == HeroSubClass.FREERUNNER && !isStarving()) {
+ evasion *= 2;
+ }
+
+ return (int)((defenseSkill - aEnc) * evasion);
+ } else {
+ return (int)(defenseSkill * evasion);
+ }
+ }
+ }
+
+ @Override
+ public int dr() {
+ int dr = belongings.armor != null ? Math.max( belongings.armor.DR, 0 ) : 0;
+ Barkskin barkskin = buff( Barkskin.class );
+ if (barkskin != null) {
+ dr += barkskin.level();
+ }
+ return dr;
+ }
+
+ @Override
+ public int damageRoll() {
+ int dmg;
+ if (belongings.weapon != null) {
+ dmg = belongings.weapon.damageRoll( this );
+ } else {
+ dmg = STR() > 10 ? Random.IntRange( 1, STR() - 9 ) : 1;
+ }
+ return buff( Fury.class ) != null ? (int)(dmg * 1.5f) : dmg;
+ }
+
+ @Override
+ public float speed() {
+
+ int aEnc = belongings.armor != null ? belongings.armor.STR - STR() : 0;
+ if (aEnc > 0) {
+
+ return (float)(super.speed() * Math.pow( 1.3, -aEnc ));
+
+ } else {
+
+ float speed = super.speed();
+ return ((HeroSprite)sprite).sprint( subClass == HeroSubClass.FREERUNNER && !isStarving() ) ? 1.6f * speed : speed;
+
+ }
+ }
+
+ public float attackDelay() {
+ if (belongings.weapon != null) {
+
+ return belongings.weapon.speedFactor( this );
+
+ } else {
+ return 1f;
+ }
+ }
+
+ @Override
+ public void spend( float time ) {
+ int hasteLevel = 0;
+ for (Buff buff : buffs( RingOfHaste.Haste.class )) {
+ hasteLevel += ((RingOfHaste.Haste)buff).level;
+ }
+ super.spend( hasteLevel == 0 ? time : (float)(time * Math.pow( 1.1, -hasteLevel )) );
+ };
+
+ public void spendAndNext( float time ) {
+ busy();
+ spend( time );
+ next();
+ }
+
+ @Override
+ public boolean act() {
+
+ super.act();
+
+ if (paralysed) {
+
+ curAction = null;
+
+ spendAndNext( TICK );
+ return false;
+ }
+
+ checkVisibleMobs();
+ AttackIndicator.updateState();
+
+ if (curAction == null) {
+
+ if (restoreHealth) {
+ if (isStarving() || HP >= HT) {
+ restoreHealth = false;
+ } else {
+ spend( TIME_TO_REST ); next();
+ return false;
+ }
+ }
+
+ ready();
+
+ } else {
+
+ restoreHealth = false;
+
+ ready = false;
+
+ if (curAction instanceof HeroAction.Move) {
+
+ actMove( (HeroAction.Move)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Interact) {
+
+ actInteract( (HeroAction.Interact)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Buy) {
+
+ actBuy( (HeroAction.Buy)curAction );
+
+ }else
+ if (curAction instanceof HeroAction.PickUp) {
+
+ actPickUp( (HeroAction.PickUp)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.OpenChest) {
+
+ actOpenChest( (HeroAction.OpenChest)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Unlock) {
+
+ actUnlock( (HeroAction.Unlock)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Descend) {
+
+ actDescend( (HeroAction.Descend)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Ascend) {
+
+ actAscend( (HeroAction.Ascend)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Attack) {
+
+ actAttack( (HeroAction.Attack)curAction );
+
+ } else
+ if (curAction instanceof HeroAction.Cook) {
+
+ actCook( (HeroAction.Cook)curAction );
+
+ }
+ }
+
+ return false;
+ }
+
+ public void busy() {
+ ready = false;
+ }
+
+ private void ready() {
+ sprite.idle();
+ curAction = null;
+ ready = true;
+
+ GameScene.ready();
+ }
+
+ public void interrupt() {
+ if (curAction != null && curAction.dst != pos) {
+ lastAction = curAction;
+ }
+ curAction = null;
+ }
+
+ public void resume() {
+ curAction = lastAction;
+ lastAction = null;
+ act();
+ }
+
+ private void actMove( HeroAction.Move action ) {
+
+ if (getCloser( action.dst )) {
+
+ } else {
+ if (Dungeon.level.map[pos] == Terrain.SIGN) {
+ GameScene.show( new WndMessage( Dungeon.tip() ) );
+ }
+ ready();
+ }
+ }
+
+ private void actInteract( HeroAction.Interact action ) {
+
+ Mob.NPC npc = action.npc;
+
+ if (Level.adjacent( pos, npc.pos )) {
+
+ ready();
+ sprite.turnTo( pos, npc.pos );
+ npc.interact();
+
+ } else {
+
+ if (Level.fieldOfView[npc.pos] && getCloser( npc.pos )) {
+
+ } else {
+ ready();
+ }
+
+ }
+ }
+
+ private void actBuy( HeroAction.Buy action ) {
+ int dst = action.dst;
+ if (pos == dst || Level.adjacent( pos, dst )) {
+
+ ready();
+
+ Heap heap = Dungeon.level.heaps.get( dst );
+ if (heap != null && heap.type == Type.FOR_SALE && heap.size() == 1) {
+ GameScene.show( new WndTradeItem( heap, true ) );
+ }
+
+ } else if (getCloser( dst )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actCook( HeroAction.Cook action ) {
+ int dst = action.dst;
+ if (Dungeon.visible[dst]) {
+
+ ready();
+ AlchemyPot.operate( this, dst );
+
+ } else if (getCloser( dst )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actPickUp( HeroAction.PickUp action ) {
+ int dst = action.dst;
+ if (pos == dst) {
+
+ Heap heap = Dungeon.level.heaps.get( pos );
+ if (heap != null) {
+ Item item = heap.pickUp();
+ if (item.doPickUp( this )) {
+
+ if (item instanceof Dewdrop) {
+
+ } else {
+
+ if ((item instanceof ScrollOfUpgrade && ((ScrollOfUpgrade)item).isKnown()) ||
+ (item instanceof PotionOfStrength && ((PotionOfStrength)item).isKnown())) {
+ GLog.p( TXT_YOU_NOW_HAVE, item.name() );
+ } else {
+ GLog.i( TXT_YOU_NOW_HAVE, item.name() );
+ }
+ }
+
+ if (!heap.isEmpty()) {
+ GLog.i( TXT_SOMETHING_ELSE );
+ }
+ curAction = null;
+ } else {
+ Dungeon.level.drop( item, pos ).sprite.drop();
+ ready();
+ }
+ } else {
+ ready();
+ }
+
+ } else if (getCloser( dst )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actOpenChest( HeroAction.OpenChest action ) {
+ int dst = action.dst;
+ if (Level.adjacent( pos, dst ) || pos == dst) {
+
+ Heap heap = Dungeon.level.heaps.get( dst );
+ if (heap != null &&
+ (heap.type == Type.CHEST || heap.type == Type.TOMB || heap.type == Type.SKELETON ||
+ heap.type == Type.LOCKED_CHEST || heap.type == Type.CRYSTAL_CHEST)) {
+
+ theKey = null;
+
+ if (heap.type == Type.LOCKED_CHEST || heap.type == Type.CRYSTAL_CHEST) {
+
+ theKey = belongings.getKey( GoldenKey.class, Dungeon.depth );
+
+ if (theKey == null) {
+ GLog.w( TXT_LOCKED_CHEST );
+ ready();
+ return;
+ }
+ }
+
+ switch (heap.type) {
+ case TOMB:
+ Sample.INSTANCE.play( Assets.SND_TOMB );
+ Camera.main.shake( 1, 0.5f );
+ break;
+ case SKELETON:
+ break;
+ default:
+ Sample.INSTANCE.play( Assets.SND_UNLOCK );
+ }
+
+ spend( Key.TIME_TO_UNLOCK );
+ sprite.operate( dst );
+
+ } else {
+ ready();
+ }
+
+ } else if (getCloser( dst )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actUnlock( HeroAction.Unlock action ) {
+ int doorCell = action.dst;
+ if (Level.adjacent( pos, doorCell )) {
+
+ theKey = null;
+ int door = Dungeon.level.map[doorCell];
+
+ if (door == Terrain.LOCKED_DOOR) {
+
+ theKey = belongings.getKey( IronKey.class, Dungeon.depth );
+
+ } else if (door == Terrain.LOCKED_EXIT) {
+
+ theKey = belongings.getKey( SkeletonKey.class, Dungeon.depth );
+
+ }
+
+ if (theKey != null) {
+
+ spend( Key.TIME_TO_UNLOCK );
+ sprite.operate( doorCell );
+
+ Sample.INSTANCE.play( Assets.SND_UNLOCK );
+
+ } else {
+ GLog.w( TXT_LOCKED_DOOR );
+ ready();
+ }
+
+ } else if (getCloser( doorCell )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actDescend( HeroAction.Descend action ) {
+ int stairs = action.dst;
+ if (pos == stairs && pos == Dungeon.level.exit) {
+
+ curAction = null;
+
+ Hunger hunger = buff( Hunger.class );
+ if (hunger != null && !hunger.isStarving()) {
+ hunger.satisfy( -Hunger.STARVING / 10 );
+ }
+
+ InterlevelScene.mode = InterlevelScene.Mode.DESCEND;
+ Game.switchScene( InterlevelScene.class );
+
+ } else if (getCloser( stairs )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actAscend( HeroAction.Ascend action ) {
+ int stairs = action.dst;
+ if (pos == stairs && pos == Dungeon.level.entrance) {
+
+ if (Dungeon.depth == 1) {
+
+ if (belongings.getItem( Amulet.class ) == null) {
+ GameScene.show( new WndMessage( TXT_LEAVE ) );
+ ready();
+ } else {
+ Dungeon.deleteGame( Dungeon.hero.heroClass, true );
+ Game.switchScene( SurfaceScene.class );
+ }
+
+ } else {
+
+ curAction = null;
+
+ Hunger hunger = buff( Hunger.class );
+ if (hunger != null && !hunger.isStarving()) {
+ hunger.satisfy( -Hunger.STARVING / 10 );
+ }
+
+ InterlevelScene.mode = InterlevelScene.Mode.ASCEND;
+ Game.switchScene( InterlevelScene.class );
+ }
+
+ } else if (getCloser( stairs )) {
+
+ } else {
+ ready();
+ }
+ }
+
+ private void actAttack( HeroAction.Attack action ) {
+
+ enemy = action.target;
+
+ if (Level.adjacent( pos, enemy.pos ) && enemy.isAlive() && !pacified) {
+
+ spend( attackDelay() );
+ sprite.attack( enemy.pos );
+
+ } else {
+
+ if (Level.fieldOfView[enemy.pos] && getCloser( enemy.pos )) {
+
+ } else {
+ ready();
+ }
+
+ }
+ }
+
+ public void rest( boolean tillHealthy ) {
+ spendAndNext( TIME_TO_REST );
+ if (!tillHealthy) {
+ sprite.showStatus( CharSprite.DEFAULT, TXT_WAIT );
+ }
+ restoreHealth = tillHealthy;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (belongings.weapon != null) {
+
+ KindOfWeapon weapon = belongings.weapon;
+ weapon.proc( this, enemy, damage );
+
+ switch (subClass) {
+ case GLADIATOR:
+ if (weapon instanceof MeleeWeapon) {
+ damage += Buff.affect( this, Combo.class ).hit( enemy, damage );
+ }
+ break;
+ case BATTLEMAGE:
+ if (weapon instanceof Wand) {
+ Wand wand = (Wand)weapon;
+ if (wand.curCharges < wand.maxCharges && damage > 0) {
+
+ wand.curCharges++;
+ if (Dungeon.quickslot == wand) {
+ QuickSlot.refresh();
+ }
+
+ ScrollOfRecharging.charge( this );
+ }
+ damage += wand.curCharges;
+ }
+ case SNIPER:
+ if (usingRanged) {
+ Buff.prolong( enemy, SnipersMark.class, attackDelay() * 1.1f );
+ }
+ break;
+ default:
+ }
+ }
+
+ return damage;
+ }
+
+ @Override
+ public int defenseProc( Char enemy, int damage ) {
+
+ RingOfThorns.Thorns thorns = buff( RingOfThorns.Thorns.class );
+ if (thorns != null) {
+ int dmg = Random.IntRange( 0, damage );
+ if (dmg > 0) {
+ enemy.damage( dmg, thorns );
+ }
+ }
+
+ Earthroot.Armor armor = buff( Earthroot.Armor.class );
+ if (armor != null) {
+ damage = armor.absorb( damage );
+ }
+
+ if (belongings.armor != null) {
+ damage = belongings.armor.proc( enemy, this, damage );
+ }
+
+ return damage;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ restoreHealth = false;
+ super.damage( dmg, src );
+
+ if (subClass == HeroSubClass.BERSERKER && 0 < HP && HP <= HT * Fury.LEVEL) {
+ Buff.affect( this, Fury.class );
+ }
+ }
+
+ private void checkVisibleMobs() {
+
+ ArrayList visible = new ArrayList();
+
+ boolean newMob = false;
+
+ for (Mob m : Dungeon.level.mobs) {
+ if (Level.fieldOfView[ m.pos ] && m.hostile) {
+ visible.add( m );
+ if (!visibleEnemies.contains( m )) {
+ newMob = true;
+ }
+ }
+ }
+
+ if (newMob) {
+ interrupt();
+ restoreHealth = false;
+ }
+
+ visibleEnemies = visible;
+ }
+
+ public int visibleEnemies() {
+ return visibleEnemies.size();
+ }
+
+ public Mob visibleEnemy( int index ) {
+ return visibleEnemies.get( index % visibleEnemies.size() );
+ }
+
+ private boolean getCloser( final int target ) {
+
+ if (rooted) {
+ return false;
+ }
+
+ int step = -1;
+
+ if (Level.adjacent( pos, target )) {
+
+ if (Actor.findChar( target ) == null) {
+ if (Level.pit[target] && !flying && !Chasm.jumpConfirmed) {
+ Chasm.heroJump( this );
+ interrupt();
+ return false;
+ }
+ if (Level.passable[target] || Level.avoid[target]) {
+ step = target;
+ }
+ }
+
+ } else {
+
+ int len = Level.LENGTH;
+ boolean[] p = Level.passable;
+ boolean[] v = Dungeon.level.visited;
+ boolean[] m = Dungeon.level.mapped;
+ boolean[] passable = new boolean[len];
+ for (int i=0; i < len; i++) {
+ passable[i] = p[i] && (v[i] || m[i]);
+ }
+
+ step = Dungeon.findPath( this, pos, target, passable, Level.fieldOfView );
+ }
+
+ if (step != -1) {
+
+ sprite.move( pos, step );
+ move( step );
+ spend( 1 / speed() );
+
+ return true;
+
+ } else {
+
+ return false;
+
+ }
+
+ }
+
+ public void handle( int cell ) {
+
+ if (cell == -1) {
+ return;
+ }
+
+ Char ch;
+ Heap heap;
+
+ if (Dungeon.level.map[cell] == Terrain.ALCHEMY && cell != pos) {
+
+ curAction = new HeroAction.Cook( cell );
+
+ } else
+ if (Level.fieldOfView[cell] && (ch = Actor.findChar( cell )) instanceof Mob) {
+
+ if (ch instanceof Mob.NPC) {
+ curAction = new HeroAction.Interact( (Mob.NPC)ch );
+ } else {
+ curAction = new HeroAction.Attack( ch );
+ }
+
+ } else if ((heap = Dungeon.level.heaps.get( cell )) != null) {
+
+ switch (heap.type) {
+ case HEAP:
+ curAction = new HeroAction.PickUp( cell );
+ break;
+ case FOR_SALE:
+ curAction = heap.size() == 1 && heap.peek().price() > 0 ?
+ new HeroAction.Buy( cell ) :
+ new HeroAction.PickUp( cell );
+ break;
+ default:
+ curAction = new HeroAction.OpenChest( cell );
+ }
+
+ } else if (Dungeon.level.map[cell] == Terrain.LOCKED_DOOR || Dungeon.level.map[cell] == Terrain.LOCKED_EXIT) {
+
+ curAction = new HeroAction.Unlock( cell );
+
+ } else if (cell == Dungeon.level.exit) {
+
+ curAction = new HeroAction.Descend( cell );
+
+ } else if (cell == Dungeon.level.entrance) {
+
+ curAction = new HeroAction.Ascend( cell );
+
+ } else {
+
+ curAction = new HeroAction.Move( cell );
+ lastAction = null;
+
+ }
+
+ act();
+ }
+
+ public void earnExp( int exp ) {
+
+ this.exp += exp;
+
+ boolean levelUp = false;
+ while (this.exp >= maxExp()) {
+ this.exp -= maxExp();
+ lvl++;
+
+ HT += 5;
+ HP += 5;
+ attackSkill++;
+ defenseSkill++;
+
+ if (lvl < 10) {
+ updateAwareness();
+ }
+
+ levelUp = true;
+ }
+
+ if (levelUp) {
+
+ GLog.p( TXT_NEW_LEVEL, lvl );
+ sprite.showStatus( CharSprite.POSITIVE, TXT_LEVEL_UP );
+ Sample.INSTANCE.play( Assets.SND_LEVELUP );
+
+ Badges.validateLevelReached();
+ }
+
+ if (subClass == HeroSubClass.WARLOCK) {
+
+ int value = Math.min( HT - HP, 1 + (Dungeon.depth - 1) / 5 );
+ if (value > 0) {
+ HP += value;
+ sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ }
+
+ ((Hunger)buff( Hunger.class )).satisfy( 10 );
+ }
+ }
+
+ public int maxExp() {
+ return 5 + lvl * 5;
+ }
+
+ void updateAwareness() {
+ awareness = (float)(1 - Math.pow(
+ (heroClass == HeroClass.ROGUE ? 0.85 : 0.90),
+ (1 + Math.min( lvl, 9 )) * 0.5
+ ));
+ }
+
+ public boolean isStarving() {
+ return ((Hunger)buff( Hunger.class )).isStarving();
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ super.add( buff );
+
+ if (sprite != null) {
+ if (buff instanceof Burning) {
+ GLog.w( "You catch fire!" );
+ interrupt();
+ } else if (buff instanceof Paralysis) {
+ GLog.w( "You are paralysed!" );
+ interrupt();
+ } else if (buff instanceof Poison) {
+ GLog.w( "You are poisoned!" );
+ interrupt();
+ } else if (buff instanceof Ooze) {
+ GLog.w( "Caustic ooze eats your flesh. Wash away it!" );
+ } else if (buff instanceof Roots) {
+ GLog.w( "You can't move!" );
+ } else if (buff instanceof Weakness) {
+ GLog.w( "You feel weakened!" );
+ } else if (buff instanceof Blindness) {
+ GLog.w( "You are blinded!" );
+ } else if (buff instanceof Fury) {
+ GLog.w( "You become furious!" );
+ sprite.showStatus( CharSprite.POSITIVE, "furious" );
+ } else if (buff instanceof Charm) {
+ GLog.w( "You are charmed!" );
+ } else if (buff instanceof Cripple) {
+ GLog.w( "You are crippled!" );
+ } else if (buff instanceof Bleeding) {
+ GLog.w( "You are bleeding!" );
+ }
+
+ else if (buff instanceof Light) {
+ sprite.add( CharSprite.State.ILLUMINATED );
+ }
+ }
+
+ BuffIndicator.refreshHero();
+ }
+
+ @Override
+ public void remove( Buff buff ) {
+ super.remove( buff );
+
+ if (buff instanceof Light) {
+ sprite.remove( CharSprite.State.ILLUMINATED );
+ }
+
+ BuffIndicator.refreshHero();
+ }
+
+ @Override
+ public int stealth() {
+ int stealth = super.stealth();
+ for (Buff buff : buffs( RingOfShadows.Shadows.class )) {
+ stealth += ((RingOfShadows.Shadows)buff).level;
+ }
+ return stealth;
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ curAction = null;
+
+ DewVial.autoDrink( this );
+ if (isAlive()) {
+ new Flare( 8, 32 ).color( 0xFFFF66, true ).show( sprite, 2f ) ;
+ return;
+ }
+
+ Actor.fixTime();
+ super.die( cause );
+
+ Ankh ankh = (Ankh)belongings.getItem( Ankh.class );
+ if (ankh == null) {
+
+ reallyDie( cause );
+
+ } else {
+
+ Dungeon.deleteGame( Dungeon.hero.heroClass, false );
+ GameScene.show( new WndResurrect( ankh, cause ) );
+
+ }
+ }
+
+ public static void reallyDie( Object cause ) {
+
+ int length = Level.LENGTH;
+ int[] map = Dungeon.level.map;
+ boolean[] visited = Dungeon.level.visited;
+ boolean[] discoverable = Level.discoverable;
+
+ for (int i=0; i < length; i++) {
+
+ int terr = map[i];
+
+ if (discoverable[i]) {
+
+ visited[i] = true;
+ if ((Terrain.flags[terr] & Terrain.SECRET) != 0) {
+ Level.set( i, Terrain.discover( terr ) );
+ GameScene.updateMap( i );
+ }
+ }
+ }
+
+ Bones.leave();
+
+ Dungeon.observe();
+
+ Dungeon.hero.belongings.identify();
+
+ GameScene.gameOver();
+
+ if (cause instanceof Hero.Doom) {
+ ((Hero.Doom)cause).onDeath();
+ }
+
+ Dungeon.deleteGame( Dungeon.hero.heroClass, true );
+ }
+
+ @Override
+ public void move( int step ) {
+ super.move( step );
+
+ if (!flying) {
+
+ if (Level.water[step]) {
+ Sample.INSTANCE.play( Assets.SND_WATER, 1, 1, Random.Float( 0.8f, 1.25f ) );
+ } else {
+ Sample.INSTANCE.play( Assets.SND_STEP );
+ }
+ Dungeon.level.press( step, this );
+ }
+ }
+
+ @Override
+ public void onMotionComplete() {
+
+ Dungeon.observe();
+ search( false );
+
+ super.onMotionComplete();
+ }
+
+ @Override
+ public void onAttackComplete() {
+
+ AttackIndicator.target( enemy );
+
+ attack( enemy );
+ curAction = null;
+
+ Invisibility.dispel();
+
+ super.onAttackComplete();
+ }
+
+ @Override
+ public void onOperateComplete() {
+
+ if (curAction instanceof HeroAction.Unlock) {
+
+ if (theKey != null) {
+ theKey.detach( belongings.backpack );
+ theKey = null;
+ }
+
+ int doorCell = ((HeroAction.Unlock)curAction).dst;
+ int door = Dungeon.level.map[doorCell];
+
+ Level.set( doorCell, door == Terrain.LOCKED_DOOR ? Terrain.DOOR : Terrain.UNLOCKED_EXIT );
+ GameScene.updateMap( doorCell );
+
+ } else if (curAction instanceof HeroAction.OpenChest) {
+
+ if (theKey != null) {
+ theKey.detach( belongings.backpack );
+ theKey = null;
+ }
+
+ Heap heap = Dungeon.level.heaps.get( ((HeroAction.OpenChest)curAction).dst );
+ if (heap.type == Type.SKELETON) {
+ Sample.INSTANCE.play( Assets.SND_BONES );
+ }
+ heap.open( this );
+ }
+ curAction = null;
+
+ super.onOperateComplete();
+ }
+
+ public boolean search( boolean intentional ) {
+
+ boolean smthFound = false;
+
+ int positive = 0;
+ int negative = 0;
+ for (Buff buff : buffs( RingOfDetection.Detection.class )) {
+ int bonus = ((RingOfDetection.Detection)buff).level;
+ if (bonus > positive) {
+ positive = bonus;
+ } else if (bonus < 0) {
+ negative += bonus;
+ }
+ }
+ int distance = 1 + positive + negative;
+
+ float level = intentional ? (2 * awareness - awareness * awareness) : awareness;
+ if (distance <= 0) {
+
+ level /= 2 - distance;
+ distance = 1;
+ }
+
+ int cx = pos % Level.WIDTH;
+ int cy = pos / Level.WIDTH;
+ int ax = cx - distance;
+ if (ax < 0) {
+ ax = 0;
+ }
+ int bx = cx + distance;
+ if (bx >= Level.WIDTH) {
+ bx = Level.WIDTH - 1;
+ }
+ int ay = cy - distance;
+ if (ay < 0) {
+ ay = 0;
+ }
+ int by = cy + distance;
+ if (by >= Level.HEIGHT) {
+ by = Level.HEIGHT - 1;
+ }
+
+ for (int y = ay; y <= by; y++) {
+ for (int x = ax, p = ax + y * Level.WIDTH; x <= bx; x++, p++) {
+
+ if (Dungeon.visible[p]) {
+
+ if (intentional) {
+ sprite.parent.addToBack( new CheckedCell( p ) );
+ }
+
+ if (Level.secret[p] && (intentional || Random.Float() < level)) {
+
+ int oldValue = Dungeon.level.map[p];
+
+ GameScene.discoverTile( p, oldValue );
+
+ Level.set( p, Terrain.discover( oldValue ) );
+
+ GameScene.updateMap( p );
+
+ ScrollOfMagicMapping.discover( p );
+
+ smthFound = true;
+ }
+ }
+ }
+ }
+
+
+ if (intentional) {
+ sprite.showStatus( CharSprite.DEFAULT, TXT_SEARCH );
+ sprite.operate( pos );
+ if (smthFound) {
+ spendAndNext( Random.Float() < level ? TIME_TO_SEARCH : TIME_TO_SEARCH * 2 );
+ } else {
+ spendAndNext( TIME_TO_SEARCH );
+ }
+
+ }
+
+ if (smthFound) {
+ GLog.w( TXT_NOTICED_SMTH );
+ Sample.INSTANCE.play( Assets.SND_SECRET );
+ interrupt();
+ }
+
+ return smthFound;
+ }
+
+ public void resurrect( int resetLevel ) {
+
+ HP = HT;
+ Dungeon.gold = 0;
+ exp = 0;
+
+ belongings.resurrect( resetLevel );
+
+ live();
+ }
+
+ @Override
+ public HashSet> resistances() {
+ RingOfElements.Resistance r = buff( RingOfElements.Resistance.class );
+ return r == null ? super.resistances() : r.resistances();
+ }
+
+ @Override
+ public HashSet> immunities() {
+ GasesImmunity buff = buff( GasesImmunity.class );
+ return buff == null ? super.immunities() : GasesImmunity.IMMUNITIES;
+ }
+
+ public static interface Doom {
+ public void onDeath();
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/hero/HeroAction.java b/src/com/watabou/pixeldungeon/actors/hero/HeroAction.java
new file mode 100644
index 0000000000..1c51ff5a44
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/hero/HeroAction.java
@@ -0,0 +1,89 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.hero;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+
+
+public class HeroAction {
+
+ public int dst;
+
+ public static class Move extends HeroAction {
+ public Move( int dst ) {
+ this.dst = dst;
+ }
+ }
+
+ public static class PickUp extends HeroAction {
+ public PickUp( int dst ) {
+ this.dst = dst;
+ }
+ }
+
+ public static class OpenChest extends HeroAction {
+ public OpenChest( int dst ) {
+ this.dst = dst;
+ }
+ }
+
+ public static class Buy extends HeroAction {
+ public Buy( int dst ) {
+ this.dst = dst;
+ }
+ }
+
+ public static class Interact extends HeroAction {
+ public Mob.NPC npc;
+ public Interact( Mob.NPC npc ) {
+ this.npc = npc;
+ }
+ }
+
+ public static class Unlock extends HeroAction {
+ public Unlock( int door ) {
+ this.dst = door;
+ }
+ }
+
+ public static class Descend extends HeroAction {
+ public Descend( int stairs ) {
+ this.dst = stairs;
+ }
+ }
+
+ public static class Ascend extends HeroAction {
+ public Ascend( int stairs ) {
+ this.dst = stairs;
+ }
+ }
+
+ public static class Cook extends HeroAction {
+ public Cook( int pot ) {
+ this.dst = pot;
+ }
+ }
+
+ public static class Attack extends HeroAction {
+ public Char target;
+ public Attack( Char target ) {
+ this.target = target;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/hero/HeroClass.java b/src/com/watabou/pixeldungeon/actors/hero/HeroClass.java
new file mode 100644
index 0000000000..7733dd01d0
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/hero/HeroClass.java
@@ -0,0 +1,221 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.hero;
+
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.items.TomeOfMastery;
+import com.watabou.pixeldungeon.items.armor.ClothArmor;
+import com.watabou.pixeldungeon.items.food.Food;
+import com.watabou.pixeldungeon.items.potions.PotionOfStrength;
+import com.watabou.pixeldungeon.items.rings.RingOfShadows;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfIdentify;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfMagicMapping;
+import com.watabou.pixeldungeon.items.wands.WandOfMagicMissile;
+import com.watabou.pixeldungeon.items.weapon.melee.Dagger;
+import com.watabou.pixeldungeon.items.weapon.melee.Knuckles;
+import com.watabou.pixeldungeon.items.weapon.melee.ShortSword;
+import com.watabou.pixeldungeon.items.weapon.missiles.Dart;
+import com.watabou.pixeldungeon.items.weapon.missiles.Boomerang;
+import com.watabou.utils.Bundle;
+
+public enum HeroClass {
+
+ WARRIOR( "warrior" ), MAGE( "mage" ), ROGUE( "rogue" ), HUNTRESS( "huntress" );
+
+ private String title;
+
+ private HeroClass( String title ) {
+ this.title = title;
+ }
+
+ public static final String[] WAR_PERKS = {
+ "Warriors start with 11 points of Strength.",
+ "Warriors start with a unique short sword. This sword can be later \"reforged\" to upgrade another melee weapon.",
+ "Warriors are less proficient with missile weapons.",
+ "Any piece of food restores some health when eaten.",
+ "Potions of Strength are identified from the beginning.",
+ };
+
+ public static final String[] MAG_PERKS = {
+ "Mages start with a unique Wand of Magic Missile. This wand can be later \"disenchanted\" to upgrade another wand.",
+ "Mages recharge their wands faster.",
+ "When eaten, any piece of food restores 1 charge for all wands in the inventory.",
+ "Mages can use wands as a melee weapon.",
+ "Scrolls of Identify are identified from the beginning."
+ };
+
+ public static final String[] ROG_PERKS = {
+ "Rogues start with a Ring of Shadows+1.",
+ "Rogues identify a type of a ring on equipping it.",
+ "Rogues are proficient with light armor, dodging better while wearing one.",
+ "Rogues are proficient in detecting hidden doors and traps.",
+ "Rogues can go without food longer.",
+ "Scrolls of Magic Mapping are identified from the beginning."
+ };
+
+ public static final String[] HUN_PERKS = {
+ "Huntresses start with 15 points of Health.",
+ "Huntresses start with a unique upgradeable boomerang.",
+ "Huntresses are proficient with missile weapons and get damage bonus for excessive strength when using them.",
+ "Huntresses gain more health from dewdrops.",
+ "Huntresses sense neighbouring monsters even if they are hidden behind obstacles."
+ };
+
+ public void initHero( Hero hero ) {
+
+ hero.heroClass = this;
+
+ switch (this) {
+ case WARRIOR:
+ initWarrior( hero );
+ break;
+
+ case MAGE:
+ initMage( hero );
+ break;
+
+ case ROGUE:
+ initRogue( hero );
+ break;
+
+ case HUNTRESS:
+ initHuntress( hero );
+ break;
+ }
+
+ hero.updateAwareness();
+ }
+
+ private static void initWarrior( Hero hero ) {
+ hero.STR = hero.STR + 1;
+
+ (hero.belongings.weapon = new ShortSword()).identify();
+ (hero.belongings.armor = new ClothArmor()).identify();
+ new Dart( 8 ).identify().collect();
+ new Food().identify().collect();
+
+ if (Badges.isUnlocked( Badges.Badge.MASTERY_WARRIOR )) {
+ new TomeOfMastery().collect();
+ }
+
+ Dungeon.quickslot = Dart.class;
+
+ new PotionOfStrength().setKnown();
+ }
+
+ private static void initMage( Hero hero ) {
+ (hero.belongings.weapon = new Knuckles()).identify();
+ (hero.belongings.armor = new ClothArmor()).identify();
+ new Food().identify().collect();
+
+ WandOfMagicMissile wand = new WandOfMagicMissile();
+ wand.identify().collect();
+
+ if (Badges.isUnlocked( Badges.Badge.MASTERY_MAGE )) {
+ new TomeOfMastery().collect();
+ }
+
+ Dungeon.quickslot = wand;
+
+ new ScrollOfIdentify().setKnown();
+ }
+
+ private static void initRogue( Hero hero ) {
+ (hero.belongings.weapon = new Dagger()).identify();
+ (hero.belongings.armor = new ClothArmor()).identify();
+ (hero.belongings.ring1 = new RingOfShadows()).upgrade().identify();
+ new Dart( 8 ).identify().collect();
+ new Food().identify().collect();
+
+ hero.belongings.ring1.activate( hero );
+
+ if (Badges.isUnlocked( Badges.Badge.MASTERY_ROGUE )) {
+ new TomeOfMastery().collect();
+ }
+
+ Dungeon.quickslot = Dart.class;
+
+ new ScrollOfMagicMapping().setKnown();
+ }
+
+ private static void initHuntress( Hero hero ) {
+
+ hero.HP = (hero.HT -= 5);
+
+ (hero.belongings.weapon = new Dagger()).identify();
+ (hero.belongings.armor = new ClothArmor()).identify();
+ Boomerang boomerang = new Boomerang();
+ boomerang.identify().collect();
+ new Food().identify().collect();
+
+ if (Badges.isUnlocked( Badges.Badge.MASTERY_HUNTRESS )) {
+ new TomeOfMastery().collect();
+ }
+
+ Dungeon.quickslot = boomerang;
+ }
+
+ public String title() {
+ return title;
+ }
+
+ public String spritesheet() {
+
+ switch (this) {
+ case WARRIOR:
+ return Assets.WARRIOR;
+ case MAGE:
+ return Assets.MAGE;
+ case ROGUE:
+ return Assets.ROGUE;
+ case HUNTRESS:
+ return Assets.HUNTRESS;
+ }
+
+ return null;
+ }
+
+ public String[] perks() {
+
+ switch (this) {
+ case WARRIOR:
+ return WAR_PERKS;
+ case MAGE:
+ return MAG_PERKS;
+ case ROGUE:
+ return ROG_PERKS;
+ case HUNTRESS:
+ return HUN_PERKS;
+ }
+
+ return null;
+ }
+
+ private static final String CLASS = "class";
+
+ public void storeInBundle( Bundle bundle ) {
+ bundle.put( CLASS, toString() );
+ }
+
+ public static HeroClass restoreInBundle( Bundle bundle ) {
+ String value = bundle.getString( CLASS );
+ return value.length() > 0 ? valueOf( value ) : ROGUE;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/hero/HeroSubClass.java b/src/com/watabou/pixeldungeon/actors/hero/HeroSubClass.java
new file mode 100644
index 0000000000..de594fc4e6
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/hero/HeroSubClass.java
@@ -0,0 +1,88 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.hero;
+
+import com.watabou.utils.Bundle;
+
+public enum HeroSubClass {
+
+ NONE( null, null ),
+
+ GLADIATOR( "gladiator",
+ "A successful attack with a melee weapon allows the _Gladiator_ to start a combo, " +
+ "in which every next successful hit inflicts more damage." ),
+
+ BERSERKER( "berserker",
+ "When severely wounded, the _Berserker_ enters a state of wild fury " +
+ "significantly increasing his damage output." ),
+
+ WARLOCK( "warlock",
+ "After killing an enemy the _Warlock_ consumes its soul. " +
+ "It heals his wounds and satisfies his hunger." ),
+
+ BATTLEMAGE( "battlemage",
+ "When fighting with a wand in his hands, the _Battlemage_ inflicts additional damage depending " +
+ "on the current number of charges. Every successful hit restores 1 charge to this wand." ),
+
+ ASSASSIN( "assassin",
+ "When performing a surprise attack, the _Assassin_ inflicts additional damage to his target." ),
+
+ FREERUNNER( "freerunner",
+ "The _Freerunner_ can move almost twice faster, than most of the monsters. When he " +
+ "is running, the Freerunner is much harder to hit. For that he must be unencumbered and not starving." ),
+
+ SNIPER( "sniper",
+ "_Snipers_ are able to detect weak points in an enemy's armor, " +
+ "effectively ignoring it when using a missile weapon." ),
+
+ WARDEN( "warden",
+ "Having a strong connection with forces of nature gives _Wardens_ an ability to gather dewdrops and " +
+ "seeds from plants. Also trampling a high grass grants them a temporary armor buff." );
+
+ private String title;
+ private String desc;
+
+ private HeroSubClass( String title, String desc ) {
+ this.title = title;
+ this.desc = desc;
+ }
+
+ public String title() {
+ return title;
+ }
+
+ public String desc() {
+ return desc;
+ }
+
+ private static final String SUBCLASS = "subClass";
+
+ public void storeInBundle( Bundle bundle ) {
+ bundle.put( SUBCLASS, toString() );
+ }
+
+ public static HeroSubClass restoreInBundle( Bundle bundle ) {
+ String value = bundle.getString( SUBCLASS );
+ try {
+ return valueOf( value );
+ } catch (Exception e) {
+ return NONE;
+ }
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Acidic.java b/src/com/watabou/pixeldungeon/actors/mobs/Acidic.java
new file mode 100644
index 0000000000..82f73399df
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Acidic.java
@@ -0,0 +1,48 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.sprites.AcidicSprite;
+import com.watabou.utils.Random;
+
+public class Acidic extends Scorpio {
+
+ {
+ name = "acidic scorpio";
+ spriteClass = AcidicSprite.class;
+ }
+
+ @Override
+ public int defenseProc( Char enemy, int damage ) {
+
+ int dmg = Random.IntRange( 0, damage );
+ if (dmg > 0) {
+ enemy.damage( dmg, this );
+ }
+
+ return super.defenseProc( enemy, damage );
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ Badges.validateRare( this );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Albino.java b/src/com/watabou/pixeldungeon/actors/mobs/Albino.java
new file mode 100644
index 0000000000..8dd19bb723
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Albino.java
@@ -0,0 +1,50 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Bleeding;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.sprites.AlbinoSprite;
+import com.watabou.utils.Random;
+
+public class Albino extends Rat {
+
+ {
+ name = "albino rat";
+ spriteClass = AlbinoSprite.class;
+
+ HP = HT = 15;
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ Badges.validateRare( this );
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 2 ) == 0) {
+ Buff.affect( enemy, Bleeding.class ).set( damage );
+ }
+
+ return damage;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Bandit.java b/src/com/watabou/pixeldungeon/actors/mobs/Bandit.java
new file mode 100644
index 0000000000..5245bfadbd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Bandit.java
@@ -0,0 +1,56 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.buffs.Blindness;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.sprites.BanditSprite;
+import com.watabou.utils.Random;
+
+public class Bandit extends Thief {
+
+ public Item item;
+
+ {
+ name = "crazy bandit";
+ spriteClass = BanditSprite.class;
+ }
+
+ @Override
+ protected boolean steal( Hero hero ) {
+ if (super.steal( hero )) {
+
+ Buff.prolong( enemy, Blindness.class, Random.Int( 5, 12 ) );
+ Dungeon.observe();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ Badges.validateRare( this );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Bat.java b/src/com/watabou/pixeldungeon/actors/mobs/Bat.java
new file mode 100644
index 0000000000..4da00ce09c
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Bat.java
@@ -0,0 +1,97 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.potions.PotionOfHealing;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Leech;
+import com.watabou.pixeldungeon.sprites.BatSprite;
+import com.watabou.utils.Random;
+
+public class Bat extends Mob {
+
+ {
+ name = "vampire bat";
+ spriteClass = BatSprite.class;
+
+ HP = HT = 30;
+ defenseSkill = 15;
+ baseSpeed = 2f;
+
+ EXP = 7;
+ maxLvl = 15;
+
+ flying = true;
+
+ loot = new PotionOfHealing();
+ lootChance = 0.125f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 6, 12 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 16;
+ }
+
+ @Override
+ public int dr() {
+ return 4;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "evaded";
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+
+ int reg = Math.min( damage, HT - HP );
+
+ if (reg > 0) {
+ HP += reg;
+ sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String description() {
+ return
+ "These brisk and tenacious inhabitants of cave domes may defeat much larger opponents by " +
+ "replenishing their health with each successful attack.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( Leech.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Bestiary.java b/src/com/watabou/pixeldungeon/actors/mobs/Bestiary.java
new file mode 100644
index 0000000000..ce0824109b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Bestiary.java
@@ -0,0 +1,183 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.utils.Random;
+
+public class Bestiary {
+
+ public static Mob mob( int depth ) {
+ @SuppressWarnings("unchecked")
+ Class extends Mob> cl = (Class extends Mob>)mobClass( depth );
+ try {
+ return cl.newInstance();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static Mob mutable( int depth ) {
+ @SuppressWarnings("unchecked")
+ Class extends Mob> cl = (Class extends Mob>)mobClass( depth );
+
+ if (Random.Int( 30 ) == 0) {
+ if (cl == Rat.class) {
+ cl = Albino.class;
+ } else if (cl == Thief.class) {
+ cl = Bandit.class;
+ } else if (cl == Brute.class) {
+ cl = Shielded.class;
+ } else if (cl == Monk.class) {
+ cl = Senior.class;
+ } else if (cl == Scorpio.class) {
+ cl = Acidic.class;
+ }
+ }
+
+ try {
+ return cl.newInstance();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static Class> mobClass( int depth ) {
+
+ float[] chances;
+ Class>[] classes;
+
+ switch (depth) {
+ case 1:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ Rat.class };
+ break;
+ case 2:
+ chances = new float[]{ 1, 1 };
+ classes = new Class>[]{ Rat.class, Gnoll.class };
+ break;
+ case 3:
+ chances = new float[]{ 1, 2, 1, 0.02f };
+ classes = new Class>[]{ Rat.class, Gnoll.class, Crab.class, Swarm.class };
+ break;
+ case 4:
+ chances = new float[]{ 1, 2, 3, 0.02f, 0.01f, 0.01f };
+ classes = new Class>[]{ Rat.class, Gnoll.class, Crab.class, Swarm.class, Skeleton.class, Thief.class };
+ break;
+
+ case 5:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ Goo.class };
+ break;
+
+ case 6:
+ chances = new float[]{ 4, 2, 1, 0.2f };
+ classes = new Class>[]{ Skeleton.class, Thief.class, Swarm.class, Shaman.class };
+ break;
+ case 7:
+ chances = new float[]{ 3, 1, 1, 1 };
+ classes = new Class>[]{ Skeleton.class, Shaman.class, Thief.class, Swarm.class };
+ break;
+ case 8:
+ chances = new float[]{ 3, 2, 1, 1, 1, 0.02f };
+ classes = new Class>[]{ Skeleton.class, Shaman.class, Gnoll.class, Thief.class, Swarm.class, Bat.class };
+ break;
+ case 9:
+ chances = new float[]{ 3, 3, 1, 1, 0.02f, 0.01f };
+ classes = new Class>[]{ Skeleton.class, Shaman.class, Thief.class, Swarm.class, Bat.class, Brute.class };
+ break;
+
+ case 10:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ Tengu.class };
+ break;
+
+ case 11:
+ chances = new float[]{ 1, 0.2f };
+ classes = new Class>[]{ Bat.class, Brute.class };
+ break;
+ case 12:
+ chances = new float[]{ 1, 1, 0.2f };
+ classes = new Class>[]{ Bat.class, Brute.class, Spinner.class };
+ break;
+ case 13:
+ chances = new float[]{ 1, 3, 1, 1, 0.02f };
+ classes = new Class>[]{ Bat.class, Brute.class, Shaman.class, Spinner.class, Elemental.class };
+ break;
+ case 14:
+ chances = new float[]{ 1, 3, 1, 4, 0.02f, 0.01f };
+ classes = new Class>[]{ Bat.class, Brute.class, Shaman.class, Spinner.class, Elemental.class, Monk.class };
+ break;
+
+ case 15:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ DM300.class };
+ break;
+
+ case 16:
+ chances = new float[]{ 1, 1, 0.2f };
+ classes = new Class>[]{ Elemental.class, Warlock.class, Monk.class };
+ break;
+ case 17:
+ chances = new float[]{ 1, 1, 1 };
+ classes = new Class>[]{ Elemental.class, Monk.class, Warlock.class };
+ break;
+ case 18:
+ chances = new float[]{ 1, 2, 1, 1 };
+ classes = new Class>[]{ Elemental.class, Monk.class, Golem.class, Warlock.class };
+ break;
+ case 19:
+ chances = new float[]{ 1, 2, 3, 1, 0.02f };
+ classes = new Class>[]{ Elemental.class, Monk.class, Golem.class, Warlock.class, Succubus.class };
+ break;
+
+ case 20:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ King.class };
+ break;
+
+ case 22:
+ chances = new float[]{ 1, 1 };
+ classes = new Class>[]{ Succubus.class, Eye.class };
+ break;
+ case 23:
+ chances = new float[]{ 1, 2, 1 };
+ classes = new Class>[]{ Succubus.class, Eye.class, Scorpio.class };
+ break;
+ case 24:
+ chances = new float[]{ 1, 2, 3 };
+ classes = new Class>[]{ Succubus.class, Eye.class, Scorpio.class };
+ break;
+
+ case 25:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ Yog.class };
+ break;
+
+ default:
+ chances = new float[]{ 1 };
+ classes = new Class>[]{ Eye.class };
+ }
+
+ return classes[ Random.chances( chances )];
+ }
+
+ public static boolean isUnique( Char mob ) {
+ return mob instanceof Goo || mob instanceof Tengu || mob instanceof DM300 || mob instanceof King || mob instanceof Yog;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Brute.java b/src/com/watabou/pixeldungeon/actors/mobs/Brute.java
new file mode 100644
index 0000000000..acc104c513
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Brute.java
@@ -0,0 +1,105 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.items.Gold;
+import com.watabou.pixeldungeon.sprites.BruteSprite;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Brute extends Mob {
+
+ private static final String TXT_ENRAGED = "%s becomes enraged!";
+
+ {
+ name = "gnoll brute";
+ spriteClass = BruteSprite.class;
+
+ HP = HT = 40;
+ defenseSkill = 15;
+
+ EXP = 8;
+ maxLvl = 15;
+
+ loot = Gold.class;
+ lootChance = 0.5f;
+ }
+
+ private boolean enraged = false;
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ enraged = HP < HT / 4;
+ }
+
+ @Override
+ public int damageRoll() {
+ return enraged ?
+ Random.NormalIntRange( 10, 40 ) :
+ Random.NormalIntRange( 8, 18 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 20;
+ }
+
+ @Override
+ public int dr() {
+ return 8;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ super.damage( dmg, src );
+
+ if (isAlive() && !enraged && HP < HT / 4) {
+ enraged = true;
+ spend( TICK );
+ if (Dungeon.visible[pos]) {
+ GLog.w( TXT_ENRAGED, name );
+ sprite.showStatus( CharSprite.NEGATIVE, "enraged" );
+ }
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "Brutes are the largest, strongest and toughest of all gnolls. When severely wounded, " +
+ "they go berserk, inflicting even more damage to their enemies.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Terror.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Crab.java b/src/com/watabou/pixeldungeon/actors/mobs/Crab.java
new file mode 100644
index 0000000000..53ccd7ce29
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Crab.java
@@ -0,0 +1,76 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Ghost;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.sprites.CrabSprite;
+import com.watabou.utils.Random;
+
+public class Crab extends Mob {
+
+ {
+ name = "sewer crab";
+ spriteClass = CrabSprite.class;
+
+ HP = HT = 15;
+ defenseSkill = 5;
+ baseSpeed = 2f;
+
+ EXP = 3;
+ maxLvl = 9;
+
+ loot = new MysteryMeat();
+ lootChance = 0.167f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 3, 6 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 12;
+ }
+
+ @Override
+ public int dr() {
+ return 4;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "parried";
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Ghost.Quest.process( pos );
+ super.die( cause );
+ }
+
+ @Override
+ public String description() {
+ return
+ "These huge crabs are at the top of the food chain in the sewers. " +
+ "They are extremely fast and their thick exoskeleton can withstand " +
+ "heavy blows.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/DM300.java b/src/com/watabou/pixeldungeon/actors/mobs/DM300.java
new file mode 100644
index 0000000000..1e943b2c83
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/DM300.java
@@ -0,0 +1,173 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.Camera;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.effects.particles.ElmoParticle;
+import com.watabou.pixeldungeon.items.keys.SkeletonKey;
+import com.watabou.pixeldungeon.items.rings.RingOfThorns;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.DM300Sprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Random;
+
+public class DM300 extends Mob {
+
+ {
+ name = "DM-300";
+ spriteClass = DM300Sprite.class;
+
+ HP = HT = 200;
+ EXP = 30;
+ defenseSkill = 18;
+
+ loot = new RingOfThorns().random();
+ lootChance = 0.333f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 18, 24 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 28;
+ }
+
+ @Override
+ public int dr() {
+ return 10;
+ }
+
+ @Override
+ public boolean act() {
+
+ GameScene.add( Blob.seed( pos, 30, ToxicGas.class ) );
+
+ return super.act();
+ }
+
+ @Override
+ public void move( int step ) {
+ super.move( step );
+
+ if (Dungeon.level.map[step] == Terrain.INACTIVE_TRAP && HP < HT) {
+
+ HP += Random.Int( 1, HT - HP );
+ sprite.emitter().burst( ElmoParticle.FACTORY, 5 );
+
+ if (Dungeon.visible[step] && Dungeon.hero.isAlive()) {
+ GLog.n( "DM-300 repairs itself!" );
+ }
+ }
+
+ int[] cells = {
+ step-1, step+1, step-Level.WIDTH, step+Level.WIDTH,
+ step-1-Level.WIDTH,
+ step-1+Level.WIDTH,
+ step+1-Level.WIDTH,
+ step+1+Level.WIDTH
+ };
+ int cell = cells[Random.Int( cells.length )];
+
+ if (Dungeon.visible[cell]) {
+ CellEmitter.get( cell ).start( Speck.factory( Speck.ROCK ), 0.07f, 10 );
+ Camera.main.shake( 3, 0.7f );
+ Sample.INSTANCE.play( Assets.SND_ROCKS );
+
+ if (Level.water[cell]) {
+ GameScene.ripple( cell );
+ } else if (Dungeon.level.map[cell] == Terrain.EMPTY) {
+ Level.set( cell, Terrain.EMPTY_DECO );
+ GameScene.updateMap( cell );
+ }
+ }
+
+ Char ch = Actor.findChar( cell );
+ if (ch != null && ch != this) {
+ Buff.prolong( ch, Paralysis.class, 2 );
+ }
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ super.die( cause );
+
+ GameScene.bossSlain();
+ Dungeon.level.drop( new SkeletonKey( Dungeon.depth ), pos ).sprite.drop();
+
+ Badges.validateBossSlain();
+
+ yell( "Mission failed. Shutting down." );
+ }
+
+ @Override
+ public void notice() {
+ super.notice();
+ yell( "Unauthorised personnel detected." );
+ }
+
+ @Override
+ public String description() {
+ return
+ "This machine was created by the Dwarves several centuries ago. Later, Dwarves started to replace machines with " +
+ "golems, elementals and even demons. Eventually it led their civilization to the decline. The DM-300 and similar " +
+ "machines were typically used for construction and mining, and in some cases, for city defense.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( ToxicGas.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Elemental.java b/src/com/watabou/pixeldungeon/actors/mobs/Elemental.java
new file mode 100644
index 0000000000..6954ce9d1b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Elemental.java
@@ -0,0 +1,110 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Frost;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.potions.PotionOfLiquidFlame;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.wands.WandOfFirebolt;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Fire;
+import com.watabou.pixeldungeon.sprites.ElementalSprite;
+import com.watabou.utils.Random;
+
+public class Elemental extends Mob {
+
+ {
+ name = "fire elemental";
+ spriteClass = ElementalSprite.class;
+
+ HP = HT = 65;
+ defenseSkill = 20;
+
+ EXP = 10;
+ maxLvl = 20;
+
+ flying = true;
+
+ loot = new PotionOfLiquidFlame();
+ lootChance = 0.1f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 16, 20 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 25;
+ }
+
+ @Override
+ public int dr() {
+ return 5;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 2 ) == 0) {
+ Buff.affect( enemy, Burning.class ).reignite( enemy );
+ }
+
+ return damage;
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ if (buff instanceof Burning) {
+ if (HP < HT) {
+ HP++;
+ sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ }
+ } else {
+ if (buff instanceof Frost) {
+ damage( Random.NormalIntRange( 1, HT * 2 / 3 ), buff );
+ }
+ super.add( buff );
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "Wandering fire elementals are a byproduct of summoning greater entities. " +
+ "They are too chaotic in their nature to be controlled by even the most powerful demonologist.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Burning.class );
+ IMMUNITIES.add( Fire.class );
+ IMMUNITIES.add( WandOfFirebolt.class );
+ IMMUNITIES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Eye.java b/src/com/watabou/pixeldungeon/actors/mobs/Eye.java
new file mode 100644
index 0000000000..79c24982a5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Eye.java
@@ -0,0 +1,174 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.particles.PurpleParticle;
+import com.watabou.pixeldungeon.items.Dewdrop;
+import com.watabou.pixeldungeon.items.wands.WandOfDisintegration;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Leech;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.EyeSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Random;
+
+public class Eye extends Mob {
+
+ private static final String TXT_DEATHGAZE_KILLED = "%s's deathgaze killed you...";
+
+ {
+ name = "evil eye";
+ spriteClass = EyeSprite.class;
+
+ HP = HT = 100;
+ defenseSkill = 20;
+ viewDistance = Light.DISTANCE;
+
+ EXP = 13;
+ maxLvl = 25;
+
+ flying = true;
+
+ loot = new Dewdrop();
+ lootChance = 0.5f;
+ }
+
+ @Override
+ public int dr() {
+ return 10;
+ }
+
+ private int hitCell;
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+
+ hitCell = Ballistica.cast( pos, enemy.pos, true, false );
+
+ for (int i=1; i < Ballistica.distance; i++) {
+ if (Ballistica.trace[i] == enemy.pos) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 30;
+ }
+
+ @Override
+ protected float attackDelay() {
+ return 1.6f;
+ }
+
+ @Override
+ protected boolean doAttack( Char enemy ) {
+
+ spend( attackDelay() );
+
+ boolean rayVisible = false;
+
+ for (int i=0; i < Ballistica.distance; i++) {
+ if (Dungeon.visible[Ballistica.trace[i]]) {
+ rayVisible = true;
+ }
+ }
+
+ if (rayVisible) {
+ sprite.attack( hitCell );
+ return false;
+ } else {
+ attack( enemy );
+ return true;
+ }
+ }
+
+ @Override
+ public boolean attack( Char enemy ) {
+
+ for (int i=1; i < Ballistica.distance; i++) {
+
+ int pos = Ballistica.trace[i];
+
+ Char ch = Actor.findChar( pos );
+ if (ch == null) {
+ continue;
+ }
+
+ if (hit( this, ch, true )) {
+ ch.damage( Random.NormalIntRange( 14, 20 ), this );
+
+ if (Dungeon.visible[pos]) {
+ ch.sprite.flash();
+ CellEmitter.center( pos ).burst( PurpleParticle.BURST, Random.IntRange( 1, 2 ) );
+ }
+
+ if (!ch.isAlive() && ch == Dungeon.hero) {
+ Dungeon.fail( Utils.format( ResultDescriptions.MOB, Utils.indefinite( name ), Dungeon.depth ) );
+ GLog.n( TXT_DEATHGAZE_KILLED, name );
+ }
+ } else {
+ ch.sprite.showStatus( CharSprite.NEUTRAL, ch.defenseVerb() );
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return
+ "One of this demon's other names is \"orb of hatred\", because when it sees an enemy, " +
+ "it uses its deathgaze recklessly, often ignoring its allies and wounding them.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( WandOfDisintegration.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( Leech.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Terror.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Gnoll.java b/src/com/watabou/pixeldungeon/actors/mobs/Gnoll.java
new file mode 100644
index 0000000000..caa13a4708
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Gnoll.java
@@ -0,0 +1,69 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Ghost;
+import com.watabou.pixeldungeon.items.Gold;
+import com.watabou.pixeldungeon.sprites.GnollSprite;
+import com.watabou.utils.Random;
+
+public class Gnoll extends Mob {
+
+ {
+ name = "gnoll scout";
+ spriteClass = GnollSprite.class;
+
+ HP = HT = 12;
+ defenseSkill = 4;
+
+ EXP = 2;
+ maxLvl = 8;
+
+ loot = Gold.class;
+ lootChance = 0.5f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 2, 5 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 11;
+ }
+
+ @Override
+ public int dr() {
+ return 2;
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Ghost.Quest.process( pos );
+ super.die( cause );
+ }
+
+ @Override
+ public String description() {
+ return
+ "Gnolls are hyena-like humanoids. They dwell in sewers and dungeons, venturing up to raid the surface from time to time. " +
+ "Gnoll scouts are regular members of their pack, they are not as strong as brutes and not as intelligent as shamans.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Golem.java b/src/com/watabou/pixeldungeon/actors/mobs/Golem.java
new file mode 100644
index 0000000000..f1e633a2b7
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Golem.java
@@ -0,0 +1,105 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Amok;
+import com.watabou.pixeldungeon.actors.buffs.Sleep;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Imp;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.sprites.GolemSprite;
+import com.watabou.utils.Random;
+
+public class Golem extends Mob {
+
+ {
+ name = "golem";
+ spriteClass = GolemSprite.class;
+
+ HP = HT = 85;
+ defenseSkill = 18;
+
+ EXP = 12;
+ maxLvl = 22;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 20, 40 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 28;
+ }
+
+ @Override
+ protected float attackDelay() {
+ return 1.5f;
+ }
+
+ @Override
+ public int dr() {
+ return 12;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "blocked";
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Imp.Quest.process( this );
+
+ super.die( cause );
+ }
+
+ @Override
+ public String description() {
+ return
+ "The Dwarves tried to combine their knowledge of mechanisms with their newfound power of elemental binding. " +
+ "They used spirits of earth as the \"soul\" for the mechanical bodies of golems, which were believed to be " +
+ "most controllable of all. Despite this, the tiniest mistake in the ritual could cause an outbreak.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Amok.class );
+ IMMUNITIES.add( Terror.class );
+ IMMUNITIES.add( Sleep.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Goo.java b/src/com/watabou/pixeldungeon/actors/mobs/Goo.java
new file mode 100644
index 0000000000..041a240011
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Goo.java
@@ -0,0 +1,188 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.Camera;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Ooze;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.LloydsBeacon;
+import com.watabou.pixeldungeon.items.keys.SkeletonKey;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.SewerBossLevel;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.GooSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Random;
+
+public class Goo extends Mob {
+
+ private static final float PUMP_UP_DELAY = 2f;
+
+ {
+ name = "Goo";
+ HP = HT = 80;
+ EXP = 10;
+ defenseSkill = 12;
+ spriteClass = GooSprite.class;
+
+ loot = new LloydsBeacon();
+ lootChance = 0.333f;
+ }
+
+ private boolean pumpedUp = false;
+
+ @Override
+ public int damageRoll() {
+ if (pumpedUp) {
+ return Random.NormalIntRange( 5, 30 );
+ } else {
+ return Random.NormalIntRange( 2, 12 );
+ }
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return pumpedUp ? 30 : 15;
+ }
+
+ @Override
+ public int dr() {
+ return 2;
+ }
+
+ @Override
+ public boolean act() {
+
+ if (Level.water[pos] && HP < HT) {
+ sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ HP++;
+ }
+
+ return super.act();
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return pumpedUp ? distance( enemy ) <= 2 : super.canAttack(enemy);
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 3 ) == 0) {
+ Buff.affect( enemy, Ooze.class );
+ enemy.sprite.burst( 0x000000, 5 );
+ }
+
+ if (pumpedUp) {
+ Camera.main.shake( 3, 0.2f );
+ }
+
+ return damage;
+ }
+
+ @Override
+ protected boolean doAttack( Char enemy ) {
+ if (pumpedUp || Random.Int( 3 ) > 0) {
+
+ return super.doAttack( enemy );
+
+ } else {
+
+ pumpedUp = true;
+ spend( PUMP_UP_DELAY );
+
+ ((GooSprite)sprite).pumpUp();
+
+ if (Dungeon.visible[pos]) {
+ sprite.showStatus( CharSprite.NEGATIVE, "!!!" );
+ GLog.n( "Goo is pumping itself up!" );
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public boolean attack( Char enemy ) {
+ boolean result = super.attack( enemy );
+ pumpedUp = false;
+ return result;
+ }
+
+ @Override
+ protected boolean getCloser( int target ) {
+ pumpedUp = false;
+ return super.getCloser( target );
+ }
+
+ @Override
+ public void move( int step ) {
+ ((SewerBossLevel)Dungeon.level).seal();
+ super.move( step );
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ super.die( cause );
+
+ ((SewerBossLevel)Dungeon.level).unseal();
+
+ GameScene.bossSlain();
+ Dungeon.level.drop( new SkeletonKey( Dungeon.depth ), pos ).sprite.drop();
+
+ Badges.validateBossSlain();
+
+ yell( "glurp... glurp..." );
+ }
+
+ @Override
+ public void notice() {
+ super.notice();
+ yell( "GLURP-GLURP!" );
+ }
+
+ @Override
+ public String description() {
+ return
+ "Little known about The Goo. It's quite possible that it is not even a creature, but rather a " +
+ "conglomerate of substances from the sewers that gained rudiments of free will.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( ToxicGas.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/King.java b/src/com/watabou/pixeldungeon/actors/mobs/King.java
new file mode 100644
index 0000000000..dc534c7de8
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/King.java
@@ -0,0 +1,328 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.effects.Flare;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.ArmorKit;
+import com.watabou.pixeldungeon.items.keys.SkeletonKey;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.items.wands.WandOfDisintegration;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.CityBossLevel;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.KingSprite;
+import com.watabou.pixeldungeon.sprites.UndeadSprite;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.PathFinder;
+import com.watabou.utils.Random;
+
+public class King extends Mob {
+
+ private static final int MAX_ARMY_SIZE = 5;
+
+ {
+ name = "King of Dwarves";
+ spriteClass = KingSprite.class;
+
+ HP = HT = 300;
+ EXP = 40;
+ defenseSkill = 25;
+
+ Undead.count = 0;
+ }
+
+ private boolean nextPedestal = true;
+
+ private static final String PEDESTAL = "pedestal";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( PEDESTAL, nextPedestal );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ nextPedestal = bundle.getBoolean( PEDESTAL );
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 20, 38 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 32;
+ }
+
+ @Override
+ public int dr() {
+ return 14;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "parried";
+ }
+
+ @Override
+ protected boolean getCloser( int target ) {
+ return canTryToSummon() ?
+ super.getCloser( CityBossLevel.pedestal( nextPedestal ) ) :
+ super.getCloser( target );
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return canTryToSummon() ?
+ pos == CityBossLevel.pedestal( nextPedestal ) :
+ Level.adjacent( pos, enemy.pos );
+ }
+
+ private boolean canTryToSummon() {
+ if (Undead.count < maxArmySize()) {
+ Char ch = Actor.findChar( CityBossLevel.pedestal( nextPedestal ) );
+ return ch == this || ch == null;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean attack( Char enemy ) {
+ if (canTryToSummon() && pos == CityBossLevel.pedestal( nextPedestal )) {
+ summon();
+ return true;
+ } else {
+ if (Actor.findChar( CityBossLevel.pedestal( nextPedestal ) ) == enemy) {
+ nextPedestal = !nextPedestal;
+ }
+ return super.attack(enemy);
+ }
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ GameScene.bossSlain();
+ Dungeon.level.drop( new ArmorKit(), pos ).sprite.drop();
+ Dungeon.level.drop( new SkeletonKey( Dungeon.depth ), pos ).sprite.drop();
+
+ super.die( cause );
+
+ Badges.validateBossSlain();
+
+ yell( "You cannot kill me, " + Dungeon.hero.heroClass.title() + "... I am... immortal..." );
+ }
+
+ private int maxArmySize() {
+ return 1 + MAX_ARMY_SIZE * (HT - HP) / HT;
+ }
+
+ private void summon() {
+
+ nextPedestal = !nextPedestal;
+
+ sprite.centerEmitter().start( Speck.factory( Speck.SCREAM ), 0.4f, 2 );
+ Sample.INSTANCE.play( Assets.SND_CHALLENGE );
+
+ boolean[] passable = Level.passable.clone();
+ for (Actor actor : Actor.all()) {
+ if (actor instanceof Char) {
+ passable[((Char)actor).pos] = false;
+ }
+ }
+
+ int undeadsToSummon = maxArmySize() - Undead.count;
+
+ PathFinder.buildDistanceMap( pos, passable, undeadsToSummon );
+ PathFinder.distance[pos] = Integer.MAX_VALUE;
+ int dist = 1;
+
+ undeadLabel:
+ for (int i=0; i < undeadsToSummon; i++) {
+ do {
+ for (int j=0; j < Level.LENGTH; j++) {
+ if (PathFinder.distance[j] == dist) {
+
+ Undead undead = new Undead();
+ undead.pos = j;
+ GameScene.add( undead );
+
+ WandOfBlink.appear( undead, j );
+ new Flare( 3, 32 ).color( 0x000000, false ).show( undead.sprite, 2f ) ;
+
+ PathFinder.distance[j] = Integer.MAX_VALUE;
+
+ continue undeadLabel;
+ }
+ }
+ dist++;
+ } while (dist < undeadsToSummon);
+ }
+
+ yell( "Arise, slaves!" );
+ }
+
+ @Override
+ public void notice() {
+ super.notice();
+ yell( "How dare you!" );
+ }
+
+ @Override
+ public String description() {
+ return
+ "The last king of dwarves was known for his deep understanding of processes of life and death. " +
+ "He has persuaded members of his court to participate in a ritual, that should have granted them " +
+ "eternal youthfulness. In the end he was the only one, who got it - and an army of undead " +
+ "as a bonus.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( ToxicGas.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ RESISTANCES.add( WandOfDisintegration.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Paralysis.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+
+ public static class Undead extends Mob {
+
+ public static int count = 0;
+
+ {
+ name = "undead dwarf";
+ spriteClass = UndeadSprite.class;
+
+ HP = HT = 28;
+ defenseSkill = 15;
+
+ EXP = 0;
+
+ state = State.WANDERING;
+ }
+
+ @Override
+ protected void onAdd() {
+ count++;
+ super.onAdd();
+ }
+
+ @Override
+ protected void onRemove() {
+ count--;
+ super.onRemove();
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 12, 16 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 16;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( MAX_ARMY_SIZE ) == 0) {
+ Buff.prolong( enemy, Paralysis.class, 1 );
+ }
+
+ return damage;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ super.damage( dmg, src );
+ if (src instanceof ToxicGas) {
+ ((ToxicGas)src).clear( pos );
+ }
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+
+ if (Dungeon.visible[pos]) {
+ Sample.INSTANCE.play( Assets.SND_BONES );
+ }
+ }
+
+ @Override
+ public int dr() {
+ return 5;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "blocked";
+ }
+
+ @Override
+ public String description() {
+ return
+ "These undead dwarves, risen by the will of the King of Dwarves, were members of his court. " +
+ "They appear as skeletons with a stunning amount of facial hair.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Death.class );
+ IMMUNITIES.add( Paralysis.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Mob.java b/src/com/watabou/pixeldungeon/actors/mobs/Mob.java
new file mode 100644
index 0000000000..e42b6c1b65
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Mob.java
@@ -0,0 +1,528 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Statistics;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Amok;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Sleep;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroSubClass;
+import com.watabou.pixeldungeon.effects.Flare;
+import com.watabou.pixeldungeon.effects.Wound;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public abstract class Mob extends Char {
+
+ private static final String TXT_DIED = "You hear something died in the distance";
+
+ protected static final String TXT_NOTICE1 = "?!";
+ protected static final String TXT_RAGE = "#$%^";
+ protected static final String TXT_EXP = "%+dEXP";
+
+ public enum State {
+ SLEEPING,
+ HUNTING,
+ WANDERING,
+ FLEEING,
+ PASSIVE
+ }
+ public State state = State.SLEEPING;
+
+ public Class extends CharSprite> spriteClass;
+
+ protected int target = -1;
+
+ protected int defenseSkill = 0;
+
+ protected int EXP = 1;
+ protected int maxLvl = 30;
+
+ protected Char enemy;
+ protected boolean enemySeen;
+ protected boolean alerted = false;
+
+ protected static final float TIME_TO_WAKE_UP = 1f;
+
+ public boolean hostile = true;
+
+ // Unreachable target
+ public static final Mob DUMMY = new Mob() {
+ {
+ pos = -1;
+ }
+ };
+
+ private static final String STATE = "state";
+ private static final String TARGET = "target";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+
+ super.storeInBundle( bundle );
+
+ bundle.put( STATE, state.toString() );
+ if (state != State.SLEEPING) {
+ bundle.put( TARGET, target );
+ }
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+
+ super.restoreFromBundle( bundle );
+
+ state = State.valueOf( bundle.getString( STATE ) );
+ if (state != State.SLEEPING) {
+ target = bundle.getInt( TARGET );
+ }
+ }
+
+ public CharSprite sprite() {
+ CharSprite sprite = null;
+ try {
+ sprite = spriteClass.newInstance();
+ } catch (Exception e) {
+ }
+ return sprite;
+ }
+
+ @Override
+ protected boolean act() {
+
+ super.act();
+
+ boolean alertedNow = alerted;
+ alerted = false;
+
+ sprite.hideAlert();
+
+ if (paralysed) {
+ enemySeen = false;
+ spend( TICK );
+ return true;
+ }
+
+ enemy = chooseEnemy();
+
+ boolean enemyInFOV = enemy.isAlive() && Level.fieldOfView[enemy.pos] && enemy.invisible <= 0;
+
+ int oldPos = pos;
+
+ switch (state) {
+
+ case SLEEPING:
+ if (enemyInFOV &&
+ Random.Int( distance( enemy ) + enemy.stealth() + (enemy.flying ? 2 : 0) ) == 0) {
+
+ enemySeen = true;
+
+ notice();
+ state = State.HUNTING;
+ target = enemy.pos;
+
+ spend( TIME_TO_WAKE_UP );
+
+ } else {
+
+ enemySeen = false;
+
+ spend( TICK );
+
+ }
+ return true;
+
+ case WANDERING:
+ if (enemyInFOV && (alertedNow || Random.Int( distance( enemy ) / 2 + enemy.stealth() ) == 0)) {
+
+ enemySeen = true;
+
+ notice();
+ state = State.HUNTING;
+ target = enemy.pos;
+
+ } else {
+
+ enemySeen = false;
+
+ if (target != -1 && getCloser( target )) {
+ spend( 1 / speed() );
+ return moveSprite( oldPos, pos );
+ } else {
+ target = Dungeon.level.randomDestination();
+ spend( TICK );
+ }
+
+ }
+ return true;
+
+ case HUNTING:
+ enemySeen = enemyInFOV;
+ if (enemyInFOV && canAttack( enemy )) {
+
+ return doAttack( enemy );
+
+ } else {
+
+ if (enemyInFOV) {
+ target = enemy.pos;
+ }
+
+ if (target != -1 && getCloser( target )) {
+
+
+ spend( 1 / speed() );
+ return moveSprite( oldPos, pos );
+
+ } else {
+
+ spend( TICK );
+ state = State.WANDERING;
+ target = Dungeon.level.randomDestination(); // <--------
+ return true;
+ }
+ }
+
+ case FLEEING:
+ enemySeen = enemyInFOV;
+ if (enemyInFOV) {
+ target = enemy.pos;
+ }
+ if (target != -1 && getFurther( target )) {
+
+ spend( 1 / speed() );
+ return moveSprite( oldPos, pos );
+
+ } else {
+
+ spend( TICK );
+ nowhereToRun();
+
+ return true;
+ }
+
+ case PASSIVE:
+ enemySeen = false;
+ spend( TICK );
+ return true;
+
+ }
+
+ return true;
+
+ }
+
+ protected Char chooseEnemy() {
+
+ if (buff( Amok.class ) != null) {
+ if (enemy == Dungeon.hero || enemy == null) {
+
+ HashSet enemies = new HashSet();
+ for (Mob mob:Dungeon.level.mobs) {
+ if (mob != this && Level.fieldOfView[mob.pos]) {
+ enemies.add( mob );
+ }
+ }
+ if (enemies.size() > 0) {
+ return Random.element( enemies );
+ }
+
+ } else {
+ return enemy;
+ }
+ }
+
+ Terror terror = (Terror)buff( Terror.class );
+ if (terror != null) {
+ return terror.source;
+ }
+
+ return Dungeon.hero;
+ }
+
+ protected void nowhereToRun() {
+ }
+
+ protected boolean moveSprite( int from, int to ) {
+
+ if (sprite.isVisible() && (Dungeon.visible[from] || Dungeon.visible[to])) {
+ sprite.move( from, to );
+ return false;
+ } else {
+ sprite.place( to );
+ return true;
+ }
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ super.add( buff );
+ if (buff instanceof Amok) {
+ if (sprite != null) {
+ sprite.showStatus( CharSprite.NEGATIVE, TXT_RAGE );
+ }
+ state = State.HUNTING;
+ } else if (buff instanceof Terror) {
+ state = State.FLEEING;
+ } else if (buff instanceof Sleep) {
+ if (sprite != null) {
+ new Flare( 4, 32 ).color( 0x44ffff, true ).show( sprite, 2f ) ;
+ }
+ state = State.SLEEPING;
+ postpone( Sleep.SWS );
+ }
+ }
+
+ @Override
+ public void remove( Buff buff ) {
+ super.remove( buff );
+ if (buff instanceof Terror) {
+ sprite.showStatus( CharSprite.NEGATIVE, TXT_RAGE );
+ state = State.HUNTING;
+ }
+ }
+
+ protected boolean canAttack( Char enemy ) {
+ return Level.adjacent( pos, enemy.pos ) && !pacified;
+ }
+
+ protected boolean getCloser( int target ) {
+
+ if (rooted) {
+ return false;
+ }
+
+ int step = Dungeon.findPath( this, pos, target,
+ Level.passable,
+ Level.fieldOfView );
+ if (step != -1) {
+ move( step );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected boolean getFurther( int target ) {
+ int step = Dungeon.flee( this, pos, target,
+ Level.passable,
+ Level.fieldOfView );
+ if (step != -1) {
+ move( step );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void move( int step ) {
+ super.move( step );
+
+ if (!flying) {
+ Dungeon.level.mobPress( this );
+ }
+ }
+
+ protected float attackDelay() {
+ return 1f;
+ }
+
+ protected boolean doAttack( Char enemy ) {
+
+ boolean visible = Dungeon.visible[pos];
+
+ if (visible) {
+ sprite.attack( enemy.pos );
+ } else {
+ attack( enemy );
+ }
+
+ spend( attackDelay() );
+
+ return !visible;
+ }
+
+ @Override
+ public void onAttackComplete() {
+ attack( enemy );
+ super.onAttackComplete();
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+ return enemySeen && !paralysed ? defenseSkill : 0;
+ }
+
+ @Override
+ public int defenseProc( Char enemy, int damage ) {
+ if (!enemySeen && enemy == Dungeon.hero && ((Hero)enemy).subClass == HeroSubClass.ASSASSIN) {
+ damage += Random.Int( 1, damage );
+ Wound.hit( this );
+ }
+ return damage;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+
+ Terror.recover( this );
+
+ if (state == State.SLEEPING) {
+ state = State.WANDERING;
+ }
+ alerted = true;
+
+ super.damage( dmg, src );
+ }
+
+
+ @Override
+ public void destroy() {
+
+ super.destroy();
+
+ Dungeon.level.mobs.remove( this );
+
+ if (Dungeon.hero.isAlive()) {
+
+ if (hostile) {
+ Statistics.enemiesSlain++;
+ Badges.validateMonstersSlain();
+ Statistics.qualifiedForNoKilling = false;
+
+ if (Dungeon.nightMode) {
+ Statistics.nightHunt++;
+ } else {
+ Statistics.nightHunt = 0;
+ }
+ Badges.validateNightHunter();
+ }
+
+ if (Dungeon.hero.lvl <= maxLvl && EXP > 0) {
+ Dungeon.hero.sprite.showStatus( CharSprite.POSITIVE, TXT_EXP, EXP );
+ Dungeon.hero.earnExp( EXP );
+ }
+ }
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ super.die( cause );
+
+ if (Dungeon.hero.lvl <= maxLvl + 2) {
+ dropLoot();
+ }
+
+ if (Dungeon.hero.isAlive() && !Dungeon.visible[pos]) {
+ GLog.i( TXT_DIED );
+ }
+ }
+
+ protected Object loot = null;
+ protected float lootChance = 0;
+
+ @SuppressWarnings("unchecked")
+ protected void dropLoot() {
+ if (loot != null && Random.Float() < lootChance) {
+ Item item = null;
+ if (loot instanceof Generator.Category) {
+
+ item = Generator.random( (Generator.Category)loot );
+
+ } else if (loot instanceof Class>) {
+
+ item = Generator.random( (Class extends Item>)loot );
+
+ } else {
+
+ item = (Item)loot;
+
+ }
+ Dungeon.level.drop( item, pos ).sprite.drop();
+ }
+ }
+
+ public boolean reset() {
+ return false;
+ }
+
+ public void beckon( int cell ) {
+
+ notice();
+
+ if (state != State.HUNTING) {
+ state = State.WANDERING;
+ }
+ target = cell;
+ }
+
+ public String description() {
+ return "Real description is coming soon!";
+ }
+
+ public void notice() {
+ sprite.showAlert();
+ }
+
+ public void yell( String str ) {
+ GLog.n( "%s: \"%s\" ", name, str );
+ }
+
+ public static abstract class NPC extends Mob {
+
+ {
+ HP = HT = 1;
+ EXP = 0;
+
+ hostile = false;
+ state = State.PASSIVE;
+ }
+
+ protected void throwItem() {
+ Heap heap = Dungeon.level.heaps.get( pos );
+ if (heap != null) {
+ int n;
+ do {
+ n = pos + Level.NEIGHBOURS8[Random.Int( 8 )];
+ } while (!Level.passable[n] && !Level.avoid[n]);
+ Dungeon.level.drop( heap.pickUp(), n ).sprite.drop( pos );
+ }
+ }
+
+ @Override
+ public void beckon( int cell ) {
+ }
+
+ abstract public void interact();
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Monk.java b/src/com/watabou/pixeldungeon/actors/mobs/Monk.java
new file mode 100644
index 0000000000..f3697acafd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Monk.java
@@ -0,0 +1,120 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Amok;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Imp;
+import com.watabou.pixeldungeon.items.KindOfWeapon;
+import com.watabou.pixeldungeon.items.food.Food;
+import com.watabou.pixeldungeon.items.weapon.melee.Knuckles;
+import com.watabou.pixeldungeon.sprites.MonkSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Random;
+
+public class Monk extends Mob {
+
+ public static final String TXT_DISARM = "%s has knocked the %s from your hands!";
+
+ {
+ name = "dwarf monk";
+ spriteClass = MonkSprite.class;
+
+ HP = HT = 70;
+ defenseSkill = 30;
+
+ EXP = 11;
+ maxLvl = 21;
+
+ loot = new Food();
+ lootChance = 0.083f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 12, 16 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 30;
+ }
+
+ @Override
+ protected float attackDelay() {
+ return 0.5f;
+ }
+
+ @Override
+ public int dr() {
+ return 2;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "parried";
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Imp.Quest.process( this );
+
+ super.die( cause );
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+
+ if (Random.Int( 6 ) == 0 && enemy == Dungeon.hero) {
+
+ Hero hero = Dungeon.hero;
+ KindOfWeapon weapon = hero.belongings.weapon;
+
+ if (weapon != null && !(weapon instanceof Knuckles) && !weapon.cursed) {
+ hero.belongings.weapon = null;
+ Dungeon.level.drop( weapon, hero.pos ).sprite.drop();
+ GLog.w( TXT_DISARM, name, weapon.name() );
+ }
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String description() {
+ return
+ "These monks are fanatics, who devoted themselves to protecting their city's secrets from all aliens. " +
+ "They don't use any armor or weapons, relying solely on the art of hand-to-hand combat.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Amok.class );
+ IMMUNITIES.add( Terror.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Piranha.java b/src/com/watabou/pixeldungeon/actors/mobs/Piranha.java
new file mode 100644
index 0000000000..551f5be1b6
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Piranha.java
@@ -0,0 +1,144 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Statistics;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Frost;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.PiranhaSprite;
+import com.watabou.utils.Random;
+
+public class Piranha extends Mob {
+
+ {
+ name = "giant piranha";
+ spriteClass = PiranhaSprite.class;
+
+ baseSpeed = 2f;
+
+ EXP = 0;
+ }
+
+ public Piranha() {
+ super();
+
+ HP = HT = 10 + Dungeon.depth * 5;
+ defenseSkill = 10 + Dungeon.depth * 2;
+ }
+
+ @Override
+ protected boolean act() {
+ if (!Level.water[pos]) {
+ die( null );
+ return true;
+ } else {
+ return super.act();
+ }
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( Dungeon.depth, 4 + Dungeon.depth * 2 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 20 + Dungeon.depth * 2;
+ }
+
+ @Override
+ public int dr() {
+ return Dungeon.depth;
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Dungeon.level.drop( new MysteryMeat(), pos ).sprite.drop();
+ super.die( cause );
+
+ Statistics.piranhasKilled++;
+ Badges.validatePiranhasKilled();
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ protected boolean getCloser( int target ) {
+
+ if (rooted) {
+ return false;
+ }
+
+ int step = Dungeon.findPath( this, pos, target,
+ Level.water,
+ Level.fieldOfView );
+ if (step != -1) {
+ move( step );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected boolean getFurther( int target ) {
+ int step = Dungeon.flee( this, pos, target,
+ Level.water,
+ Level.fieldOfView );
+ if (step != -1) {
+ move( step );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "These carnivorous fish are not natural inhabitants of underground pools. " +
+ "They were bred specifically to protect flooded treasure vaults.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Burning.class );
+ IMMUNITIES.add( Paralysis.class );
+ IMMUNITIES.add( ToxicGas.class );
+ IMMUNITIES.add( Roots.class );
+ IMMUNITIES.add( Frost.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Rat.java b/src/com/watabou/pixeldungeon/actors/mobs/Rat.java
new file mode 100644
index 0000000000..cb06ea288a
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Rat.java
@@ -0,0 +1,65 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Ghost;
+import com.watabou.pixeldungeon.sprites.RatSprite;
+import com.watabou.utils.Random;
+
+public class Rat extends Mob {
+
+ {
+ name = "marsupial rat";
+ spriteClass = RatSprite.class;
+
+ HP = HT = 8;
+ defenseSkill = 3;
+
+ maxLvl = 5;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 1, 5 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 8;
+ }
+
+ @Override
+ public int dr() {
+ return 1;
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Ghost.Quest.process( pos );
+
+ super.die( cause );
+ }
+
+ @Override
+ public String description() {
+ return
+ "Marsupial rats are aggressive, but rather weak denizens " +
+ "of the sewers. They can be dangerous only in big numbers.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Scorpio.java b/src/com/watabou/pixeldungeon/actors/mobs/Scorpio.java
new file mode 100644
index 0000000000..724a43f71d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Scorpio.java
@@ -0,0 +1,117 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Cripple;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.items.potions.PotionOfHealing;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Leech;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.sprites.ScorpioSprite;
+import com.watabou.utils.Random;
+
+public class Scorpio extends Mob {
+
+ {
+ name = "scorpio";
+ spriteClass = ScorpioSprite.class;
+
+ HP = HT = 95;
+ defenseSkill = 24;
+ viewDistance = Light.DISTANCE;
+
+ EXP = 14;
+ maxLvl = 25;
+
+ loot = new PotionOfHealing();
+ lootChance = 0.125f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 20, 32 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 36;
+ }
+
+ @Override
+ public int dr() {
+ return 16;
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return !Level.adjacent( pos, enemy.pos ) && Ballistica.cast( pos, enemy.pos, false, true ) == enemy.pos;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 2 ) == 0) {
+ Buff.prolong( enemy, Cripple.class, Cripple.DURATION );
+ }
+
+ return damage;
+ }
+
+ @Override
+ protected boolean getCloser( int target ) {
+ if (state == State.HUNTING) {
+ return enemySeen && getFurther( target );
+ } else {
+ return super.getCloser( target );
+ }
+ }
+
+ @Override
+ protected void dropLoot() {
+ if (Random.Int( 8 ) == 0) {
+ Dungeon.level.drop( new PotionOfHealing(), pos ).sprite.drop();
+ } else if (Random.Int( 6 ) == 0) {
+ Dungeon.level.drop( new MysteryMeat(), pos ).sprite.drop();
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "These huge arachnid-like demonic creatures avoid close combat by all means, " +
+ "firing crippling serrated spikes from long distances.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( Leech.class );
+ RESISTANCES.add( Poison.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Senior.java b/src/com/watabou/pixeldungeon/actors/mobs/Senior.java
new file mode 100644
index 0000000000..4f1e2cc113
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Senior.java
@@ -0,0 +1,52 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.sprites.SeniorSprite;
+import com.watabou.utils.Random;
+
+public class Senior extends Monk {
+
+ {
+ name = "senior monk";
+ spriteClass = SeniorSprite.class;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 12, 20 );
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 10 ) == 0) {
+ Buff.prolong( enemy, Paralysis.class, 1.1f );
+ }
+ return super.attackProc( enemy, damage );
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ Badges.validateRare( this );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Shaman.java b/src/com/watabou/pixeldungeon/actors/mobs/Shaman.java
new file mode 100644
index 0000000000..97515f9990
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Shaman.java
@@ -0,0 +1,144 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.Camera;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.effects.particles.SparkParticle;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.traps.LightningTrap;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ShamanSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Callback;
+import com.watabou.utils.Random;
+
+public class Shaman extends Mob implements Callback {
+
+ private static final float TIME_TO_ZAP = 2f;
+
+ private static final String TXT_LIGHTNING_KILLED = "%s's lightning bolt killed you...";
+
+ {
+ name = "gnoll shaman";
+ spriteClass = ShamanSprite.class;
+
+ HP = HT = 18;
+ defenseSkill = 8;
+
+ EXP = 6;
+ maxLvl = 14;
+
+ loot = Generator.Category.SCROLL;
+ lootChance = 0.33f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 2, 6 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 11;
+ }
+
+ @Override
+ public int dr() {
+ return 4;
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return Ballistica.cast( pos, enemy.pos, false, true ) == enemy.pos;
+ }
+
+ @Override
+ protected boolean doAttack( Char enemy ) {
+
+ if (Level.distance( pos, enemy.pos ) <= 1) {
+
+ return super.doAttack( enemy );
+
+ } else {
+
+ boolean visible = Level.fieldOfView[pos] || Level.fieldOfView[enemy.pos];
+ if (visible) {
+ ((ShamanSprite)sprite).zap( enemy.pos );
+ }
+
+ spend( TIME_TO_ZAP );
+
+ if (hit( this, enemy, true )) {
+ int dmg = Random.Int( 2, 12 );
+ if (Level.water[enemy.pos] && !enemy.flying) {
+ dmg *= 1.5f;
+ }
+ enemy.damage( dmg, LightningTrap.LIGHTNING );
+
+ enemy.sprite.centerEmitter().burst( SparkParticle.FACTORY, 3 );
+ enemy.sprite.flash();
+
+ if (enemy == Dungeon.hero) {
+
+ Camera.main.shake( 2, 0.3f );
+
+ if (!enemy.isAlive()) {
+ Dungeon.fail( Utils.format( ResultDescriptions.MOB,
+ Utils.indefinite( name ), Dungeon.depth ) );
+ GLog.n( TXT_LIGHTNING_KILLED, name );
+ }
+ }
+ } else {
+ enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() );
+ }
+
+ return !visible;
+ }
+ }
+
+ @Override
+ public void call() {
+ next();
+ }
+
+ @Override
+ public String description() {
+ return
+ "The most intelligent gnolls can master shamanistic magic. Gnoll shamans prefer " +
+ "battle spells to compensate for lack of might, not hesitating to use them " +
+ "on those who question their status in a tribe.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( LightningTrap.Electricity.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Shielded.java b/src/com/watabou/pixeldungeon/actors/mobs/Shielded.java
new file mode 100644
index 0000000000..c4e4329d09
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Shielded.java
@@ -0,0 +1,47 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.sprites.ShieldedSprite;
+
+public class Shielded extends Brute {
+
+ {
+ name = "shielded brute";
+ spriteClass = ShieldedSprite.class;
+
+ defenseSkill = 20;
+ }
+
+ @Override
+ public int dr() {
+ return 10;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "blocked";
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ Badges.validateRare( this );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Skeleton.java b/src/com/watabou/pixeldungeon/actors/mobs/Skeleton.java
new file mode 100644
index 0000000000..4c9b3a3582
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Skeleton.java
@@ -0,0 +1,129 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.SkeletonSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Random;
+
+public class Skeleton extends Mob {
+
+ private static final String TXT_HERO_KILLED = "You were killed by the explosion of bones...";
+
+ {
+ name = "skeleton";
+ spriteClass = SkeletonSprite.class;
+
+ HP = HT = 25;
+ defenseSkill = 9;
+
+ EXP = 5;
+ maxLvl = 10;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 3, 8 );
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ super.die( cause );
+
+ boolean heroKilled = false;
+ for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
+ Char ch = findChar( pos + Level.NEIGHBOURS8[i] );
+ if (ch != null && ch.isAlive()) {
+ int damage = Math.max( 0, damageRoll() - Random.IntRange( 0, ch.dr() / 2 ) );
+ ch.damage( damage, this );
+ if (ch == Dungeon.hero && !ch.isAlive()) {
+ heroKilled = true;
+ }
+ }
+ }
+
+ if (Dungeon.visible[pos]) {
+ Sample.INSTANCE.play( Assets.SND_BONES );
+ }
+
+ if (heroKilled) {
+ Dungeon.fail( Utils.format( ResultDescriptions.MOB, Utils.indefinite( name ), Dungeon.depth ) );
+ GLog.n( TXT_HERO_KILLED );
+ }
+ }
+
+ @Override
+ protected void dropLoot() {
+ if (Random.Int( 5 ) == 0) {
+ Item loot = Generator.random( Generator.Category.WEAPON );
+ for (int i=0; i < 2; i++) {
+ Item l = Generator.random( Generator.Category.WEAPON );
+ if (l.level < loot.level) {
+ loot = l;
+ }
+ }
+ Dungeon.level.drop( loot, pos ).sprite.drop();
+ }
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 12;
+ }
+
+ @Override
+ public int dr() {
+ return 5;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "blocked";
+ }
+
+ @Override
+ public String description() {
+ return
+ "Skeletons are composed of corpses bones from unlucky adventurers and inhabitants of the dungeon, " +
+ "animated by emanations of evil magic from the depths below. After they have been " +
+ "damaged enough, they disintegrate in an explosion of bones.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Death.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Spinner.java b/src/com/watabou/pixeldungeon/actors/mobs/Spinner.java
new file mode 100644
index 0000000000..c93e73dbcf
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Spinner.java
@@ -0,0 +1,130 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.Web;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.SpinnerSprite;
+import com.watabou.utils.Random;
+
+public class Spinner extends Mob {
+
+ {
+ name = "cave spinner";
+ spriteClass = SpinnerSprite.class;
+
+ HP = HT = 50;
+ defenseSkill = 14;
+
+ EXP = 9;
+ maxLvl = 16;
+
+ loot = new MysteryMeat();
+ lootChance = 0.125f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 12, 16 );
+ }
+
+ @Override
+ protected void nowhereToRun() {
+ if (buff( Terror.class ) == null) {
+ state = State.HUNTING;
+ } else {
+ super.nowhereToRun();
+ }
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 20;
+ }
+
+ @Override
+ public int dr() {
+ return 6;
+ }
+
+ @Override
+ protected boolean act() {
+ boolean result = super.act();
+
+ if (state == State.FLEEING && buff( Terror.class ) == null &&
+ enemySeen && enemy.buff( Poison.class ) == null) {
+
+ state = State.HUNTING;
+ }
+ return result;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 2 ) == 0) {
+ Buff.affect( enemy, Poison.class ).set( Random.Int( 5, 7 ) * Poison.durationFactor( enemy ) );
+ state = State.FLEEING;
+ }
+
+ return damage;
+ }
+
+ @Override
+ public void move( int step ) {
+ if (state == State.FLEEING) {
+ GameScene.add( Blob.seed( pos, Random.Int( 5, 7 ), Web.class ) );
+ }
+ super.move( step );
+ }
+
+ @Override
+ public String description() {
+ return
+ "These greenish furry cave spiders try to avoid direct combat, preferring to wait in the distance " +
+ "while their victim, entangled in the spinner's excreted cobweb, slowly dies from their poisonous bite.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( Poison.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Roots.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Statue.java b/src/com/watabou/pixeldungeon/actors/mobs/Statue.java
new file mode 100644
index 0000000000..3e9ba24a3d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Statue.java
@@ -0,0 +1,170 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.weapon.Weapon;
+import com.watabou.pixeldungeon.items.weapon.Weapon.Enchantment;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Leech;
+import com.watabou.pixeldungeon.items.weapon.melee.MeleeWeapon;
+import com.watabou.pixeldungeon.sprites.StatueSprite;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Statue extends Mob {
+
+ {
+ name = "animated statue";
+ spriteClass = StatueSprite.class;
+
+ EXP = 0;
+ state = State.PASSIVE;
+ }
+
+ private Weapon weapon;
+
+ public Statue() {
+ super();
+
+ do {
+ weapon = (Weapon)Generator.random( Generator.Category.WEAPON );
+ } while (!(weapon instanceof MeleeWeapon) || weapon.level < 0);
+
+ weapon.identify();
+ weapon.enchant( Enchantment.random() );
+
+ HP = HT = 15 + Dungeon.depth * 5;
+ defenseSkill = 4 + Dungeon.depth;
+ }
+
+ private static final String WEAPON = "weapon";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( WEAPON, weapon );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ weapon = (Weapon)bundle.get( WEAPON );
+ }
+
+ @Override
+ protected boolean act() {
+ if (Dungeon.visible[pos]) {
+ Journal.add( Journal.Feature.STATUE );
+ }
+ return super.act();
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( weapon.MIN, weapon.MAX );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return (int)((9 + Dungeon.depth) * weapon.ACU);
+ }
+
+ @Override
+ protected float attackDelay() {
+ return weapon.DLY;
+ }
+
+ @Override
+ public int dr() {
+ return Dungeon.depth;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+
+ if (state == State.PASSIVE) {
+ state = State.HUNTING;
+ }
+
+ super.damage( dmg, src );
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ weapon.proc( this, enemy, damage );
+ return damage;
+ }
+
+ @Override
+ public void beckon( int cell ) {
+ }
+
+ @Override
+ public void die( Object cause ) {
+ Dungeon.level.drop( weapon, pos ).sprite.drop();
+ super.die( cause );
+ }
+
+ @Override
+ public void destroy() {
+ Journal.remove( Journal.Feature.STATUE );
+ super.destroy();
+ }
+
+ @Override
+ public boolean reset() {
+ state = State.PASSIVE;
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return
+ "You would think that it's just another ugly statue of this dungeon, but its red glowing eyes give itself away. " +
+ "While the statue itself is made of stone, the _" + weapon.name() + "_, it's wielding, looks real.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ RESISTANCES.add( ToxicGas.class );
+ RESISTANCES.add( Poison.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ IMMUNITIES.add( Leech.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Succubus.java b/src/com/watabou/pixeldungeon/actors/mobs/Succubus.java
new file mode 100644
index 0000000000..764c593241
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Succubus.java
@@ -0,0 +1,142 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Charm;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.buffs.Sleep;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfLullaby;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Leech;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.sprites.SuccubusSprite;
+import com.watabou.utils.Random;
+
+public class Succubus extends Mob {
+
+ private static final int BLINK_DELAY = 5;
+
+ private int delay = 0;
+
+ {
+ name = "succubus";
+ spriteClass = SuccubusSprite.class;
+
+ HP = HT = 80;
+ defenseSkill = 25;
+ viewDistance = Light.DISTANCE;
+
+ EXP = 12;
+ maxLvl = 25;
+
+ loot = new ScrollOfLullaby();
+ lootChance = 0.05f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 15, 25 );
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+
+ if (Random.Int( 3 ) == 0) {
+ Buff.affect( enemy, Charm.class, Charm.durationFactor( enemy ) * Random.IntRange( 2, 5 ) );
+ enemy.sprite.centerEmitter().start( Speck.factory( Speck.HEART ), 0.2f, 5 );
+ Sample.INSTANCE.play( Assets.SND_CHARMS );
+ }
+
+ return damage;
+ }
+
+ @Override
+ protected boolean getCloser( int target ) {
+ if (Level.fieldOfView[target] && Level.distance( pos, target ) > 2 && delay <= 0) {
+
+ blink( target );
+ spend( -1 / speed() );
+ return true;
+
+ } else {
+
+ delay--;
+ return super.getCloser( target );
+
+ }
+ }
+
+ private void blink( int target ) {
+
+ int cell = Ballistica.cast( pos, target, true, true );
+
+ if (Actor.findChar( cell ) != null && Ballistica.distance > 1) {
+ cell = Ballistica.trace[Ballistica.distance - 2];
+ }
+
+ WandOfBlink.appear( this, cell );
+
+ delay = BLINK_DELAY;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 40;
+ }
+
+ @Override
+ public int dr() {
+ return 10;
+ }
+
+ @Override
+ public String description() {
+ return
+ "The succubi are demons that look like seductive (in a slightly gothic way) girls. Using its magic, the succubus " +
+ "can charm a hero, who will become unable to attack anything until the charm wears off.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( Leech.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Sleep.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Swarm.java b/src/com/watabou/pixeldungeon/actors/mobs/Swarm.java
new file mode 100644
index 0000000000..9e3dcb54b0
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Swarm.java
@@ -0,0 +1,145 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.ArrayList;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.effects.Pushing;
+import com.watabou.pixeldungeon.items.potions.PotionOfHealing;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.levels.features.Door;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.SwarmSprite;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Swarm extends Mob {
+
+ {
+ name = "swarm of flies";
+ spriteClass = SwarmSprite.class;
+
+ HP = HT = 80;
+ defenseSkill = 5;
+
+ maxLvl = 10;
+
+ flying = true;
+ }
+
+ private static final float SPLIT_DELAY = 1f;
+
+ int generation = 0;
+
+ private static final String GENERATION = "generation";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( GENERATION, generation );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ generation = bundle.getInt( GENERATION );
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 1, 4 );
+ }
+
+ @Override
+ public int defenseProc( Char enemy, int damage ) {
+
+ if (HP >= damage + 2) {
+ ArrayList candidates = new ArrayList();
+ boolean[] passable = Level.passable;
+
+ int[] neighbours = {pos + 1, pos - 1, pos + Level.WIDTH, pos - Level.WIDTH};
+ for (int n : neighbours) {
+ if (passable[n] && Actor.findChar( n ) == null) {
+ candidates.add( n );
+ }
+ }
+
+ if (candidates.size() > 0) {
+
+ Swarm clone = split();
+ clone.HP = (HP - damage) / 2;
+ clone.pos = Random.element( candidates );
+ clone.state = State.HUNTING;
+
+ if (Dungeon.level.map[clone.pos] == Terrain.DOOR) {
+ Door.enter( clone.pos );
+ }
+
+ GameScene.add( clone, SPLIT_DELAY );
+ Actor.addDelayed( new Pushing( clone, pos, clone.pos ), -1 );
+
+ HP -= clone.HP;
+ }
+ }
+
+ return damage;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 12;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "evaded";
+ }
+
+ private Swarm split() {
+ Swarm clone = new Swarm();
+ clone.generation = generation + 1;
+ if (buff( Burning.class ) != null) {
+ Buff.affect( clone, Burning.class ).reignite( clone );
+ }
+ if (buff( Poison.class ) != null) {
+ Buff.affect( clone, Poison.class ).set( 2 );
+ }
+ return clone;
+ }
+
+ @Override
+ protected void dropLoot() {
+ if (Random.Int( 5 * (generation + 1) ) == 0) {
+ Dungeon.level.drop( new PotionOfHealing(), pos ).sprite.drop();
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "The deadly swarm of flies buzzes angrily. Every non-magical attack " +
+ "will split it into two smaller but equally dangerous swarms.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Tengu.java b/src/com/watabou/pixeldungeon/actors/mobs/Tengu.java
new file mode 100644
index 0000000000..be59824ca4
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Tengu.java
@@ -0,0 +1,193 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Badges.Badge;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.TomeOfMastery;
+import com.watabou.pixeldungeon.items.keys.SkeletonKey;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfMagicMapping;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.TenguSprite;
+import com.watabou.utils.Random;
+
+public class Tengu extends Mob {
+
+ private static final int JUMP_DELAY = 5;
+
+ {
+ name = "Tengu";
+ spriteClass = TenguSprite.class;
+
+ HP = HT = 120;
+ EXP = 20;
+ defenseSkill = 20;
+ }
+
+ private int timeToJump = JUMP_DELAY;
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 8, 15 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 20;
+ }
+
+ @Override
+ public int dr() {
+ return 5;
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ Badges.Badge badgeToCheck = null;
+ switch (Dungeon.hero.heroClass) {
+ case WARRIOR:
+ badgeToCheck = Badge.MASTERY_WARRIOR;
+ break;
+ case MAGE:
+ badgeToCheck = Badge.MASTERY_MAGE;
+ break;
+ case ROGUE:
+ badgeToCheck = Badge.MASTERY_ROGUE;
+ break;
+ case HUNTRESS:
+ badgeToCheck = Badge.MASTERY_HUNTRESS;
+ break;
+ }
+ if (!Badges.isUnlocked( badgeToCheck )) {
+ Dungeon.level.drop( new TomeOfMastery(), pos ).sprite.drop();
+ }
+
+ GameScene.bossSlain();
+ Dungeon.level.drop( new SkeletonKey( Dungeon.depth ), pos ).sprite.drop();
+ super.die( cause );
+
+ Badges.validateBossSlain();
+
+ yell( "Free at last..." );
+ }
+
+ @Override
+ protected boolean getCloser( int target ) {
+ if (Level.fieldOfView[target]) {
+ jump();
+ return true;
+ } else {
+ return super.getCloser( target );
+ }
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return Ballistica.cast( pos, enemy.pos, false, true ) == enemy.pos;
+ }
+
+ @Override
+ protected boolean doAttack( Char enemy ) {
+ timeToJump--;
+ if (timeToJump <= 0 && Level.adjacent( pos, enemy.pos )) {
+ jump();
+ return true;
+ } else {
+ return super.doAttack( enemy );
+ }
+ }
+
+ private void jump() {
+ timeToJump = JUMP_DELAY;
+
+ for (int i=0; i < 4; i++) {
+ int trapPos;
+ do {
+ trapPos = Random.Int( Level.LENGTH );
+ } while (!Level.fieldOfView[trapPos] || !Level.passable[trapPos]);
+
+ if (Dungeon.level.map[trapPos] == Terrain.INACTIVE_TRAP) {
+ Level.set( trapPos, Terrain.POISON_TRAP );
+ GameScene.updateMap( trapPos );
+ ScrollOfMagicMapping.discover( trapPos );
+ }
+ }
+
+ int newPos;
+ do {
+ newPos = Random.Int( Level.LENGTH );
+ } while (
+ !Level.fieldOfView[newPos] ||
+ !Level.passable[newPos] ||
+ Level.adjacent( newPos, enemy.pos ) ||
+ Actor.findChar( newPos ) != null);
+
+ sprite.move( pos, newPos );
+ move( newPos );
+
+ if (Dungeon.visible[newPos]) {
+ CellEmitter.get( newPos ).burst( Speck.factory( Speck.WOOL ), 6 );
+ Sample.INSTANCE.play( Assets.SND_PUFF );
+ }
+
+ spend( 1 / speed() );
+ }
+
+ @Override
+ public void notice() {
+ super.notice();
+ yell( "Gotcha, " + Dungeon.hero.heroClass.title() + "!" );
+ }
+
+ @Override
+ public String description() {
+ return
+ "Tengu are members of the ancient assassins clan, which is also called Tengu. " +
+ "These assassins are noted for extensive use of shuriken and traps.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( ToxicGas.class );
+ RESISTANCES.add( Poison.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Thief.java b/src/com/watabou/pixeldungeon/actors/mobs/Thief.java
new file mode 100644
index 0000000000..d73f2e636d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Thief.java
@@ -0,0 +1,156 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.Gold;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.rings.RingOfHaggler;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ThiefSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Thief extends Mob {
+
+ protected static final String TXT_STOLE = "%s stole %s from you!";
+ protected static final String TXT_CARRIES = "\n\n%s is carrying a _%s_. Stolen obviously.";
+
+ public Item item;
+
+ {
+ name = "crazy thief";
+ spriteClass = ThiefSprite.class;
+
+ HP = HT = 20;
+ defenseSkill = 12;
+
+ EXP = 5;
+ maxLvl = 10;
+
+ loot = RingOfHaggler.class;
+ lootChance = 0.01f;
+ }
+
+ private static final String ITEM = "item";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( ITEM, item );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ item = (Item)bundle.get( ITEM );
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 1, 7 );
+ }
+
+ @Override
+ protected float attackDelay() {
+ return 0.5f;
+ }
+
+ @Override
+ protected void nowhereToRun() {
+ if (buff( Terror.class ) == null) {
+ sprite.showStatus( CharSprite.NEGATIVE, TXT_RAGE );
+ state = State.HUNTING;
+ } else {
+ super.nowhereToRun();
+ }
+ }
+
+ @Override
+ public void die( Object cause ) {
+
+ super.die( cause );
+
+ if (item != null) {
+ Dungeon.level.drop( item, pos ).sprite.drop();
+ }
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 12;
+ }
+
+ @Override
+ public int dr() {
+ return 3;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (item == null && enemy instanceof Hero && steal( (Hero)enemy )) {
+ state = State.FLEEING;
+ }
+
+ return damage;
+ }
+
+ @Override
+ public int defenseProc(Char enemy, int damage) {
+ if (state == State.FLEEING) {
+ Dungeon.level.drop( new Gold(), pos ).sprite.drop();
+ }
+
+ return damage;
+ }
+
+ protected boolean steal( Hero hero ) {
+
+ Item item = hero.belongings.randomUnequipped();
+ if (item != null) {
+
+ GLog.w( TXT_STOLE, this.name, item.name() );
+
+ item.detachAll( hero.belongings.backpack );
+ this.item = item;
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String description() {
+ String desc =
+ "Deeper levels of the dungeon have always been a hiding place for all kinds of criminals. " +
+ "Not all of them could keep a clear mind during their extended periods so far from daylight. Long ago, " +
+ "these crazy thieves and bandits have forgotten who they are and why they steal.";
+
+ if (item != null) {
+ desc += String.format( TXT_CARRIES, Utils.capitalize( this.name ), item.name() );
+ }
+
+ return desc;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Warlock.java b/src/com/watabou/pixeldungeon/actors/mobs/Warlock.java
new file mode 100644
index 0000000000..de3e863339
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Warlock.java
@@ -0,0 +1,145 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Weakness;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.WarlockSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Callback;
+import com.watabou.utils.Random;
+
+public class Warlock extends Mob implements Callback {
+
+ private static final float TIME_TO_ZAP = 1f;
+
+ private static final String TXT_SHADOWBOLT_KILLED = "%s's shadow bolt killed you...";
+
+ {
+ name = "dwarf warlock";
+ spriteClass = WarlockSprite.class;
+
+ HP = HT = 70;
+ defenseSkill = 18;
+
+ EXP = 11;
+ maxLvl = 21;
+
+ loot = Generator.Category.POTION;
+ lootChance = 0.83f;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 12, 20 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 25;
+ }
+
+ @Override
+ public int dr() {
+ return 8;
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return Ballistica.cast( pos, enemy.pos, false, true ) == enemy.pos;
+ }
+
+ protected boolean doAttack( Char enemy ) {
+
+ if (Level.adjacent( pos, enemy.pos )) {
+
+ return super.doAttack( enemy );
+
+ } else {
+
+ boolean visible = Level.fieldOfView[pos] || Level.fieldOfView[enemy.pos];
+ if (visible) {
+ ((WarlockSprite)sprite).zap( enemy.pos );
+ } else {
+ zap();
+ }
+
+ return !visible;
+ }
+ }
+
+ private void zap() {
+ spend( TIME_TO_ZAP );
+
+ if (hit( this, enemy, true )) {
+ if (enemy == Dungeon.hero && Random.Int( 2 ) == 0) {
+ Buff.prolong( enemy, Weakness.class, Weakness.duration( enemy ) );
+ }
+
+ int dmg = Random.Int( 12, 18 );
+ enemy.damage( dmg, this );
+
+ if (!enemy.isAlive() && enemy == Dungeon.hero) {
+ Dungeon.fail( Utils.format( ResultDescriptions.MOB,
+ Utils.indefinite( name ), Dungeon.depth ) );
+ GLog.n( TXT_SHADOWBOLT_KILLED, name );
+ }
+ } else {
+ enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() );
+ }
+ }
+
+ public void onZapComplete() {
+ zap();
+ next();
+ }
+
+ @Override
+ public void call() {
+ next();
+ }
+
+ @Override
+ public String description() {
+ return
+ "When dwarves' interests have shifted from engineering to arcane arts, " +
+ "warlocks have come to power in the city. They started with elemental magic, " +
+ "but soon switched to demonology and necromancy.";
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( Death.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Wraith.java b/src/com/watabou/pixeldungeon/actors/mobs/Wraith.java
new file mode 100644
index 0000000000..2a05e26d0b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Wraith.java
@@ -0,0 +1,139 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.tweeners.AlphaTweener;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.effects.particles.ShadowParticle;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.WraithSprite;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Wraith extends Mob {
+
+ private static final float SPAWN_DELAY = 2f;
+
+ private int level;
+
+ {
+ name = "wraith";
+ spriteClass = WraithSprite.class;
+
+ HP = HT = 1;
+ EXP = 0;
+
+ flying = true;
+ }
+
+ private static final String LEVEL = "level";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( LEVEL, level );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ level = bundle.getInt( LEVEL );
+ adjustStats( level );
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 1, 3 + level );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 10 + level;
+ }
+
+ public void adjustStats( int level ) {
+ this.level = level;
+ defenseSkill = attackSkill( null ) * 5;
+ enemySeen = true;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "evaded";
+ }
+
+ @Override
+ public boolean reset() {
+ state = State.WANDERING;
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return
+ "A wraith is a vengeful spirit of a sinner, whose grave or tomb was disturbed. " +
+ "Being an ethereal entity, it is very hard to hit with a regular weapon.";
+ }
+
+ public static void spawnAround( int pos ) {
+ for (int n : Level.NEIGHBOURS4) {
+ int cell = pos + n;
+ if (Level.passable[cell] && Actor.findChar( cell ) == null) {
+ spawnAt( cell );
+ }
+ }
+ }
+
+ public static Wraith spawnAt( int pos ) {
+ if (Level.passable[pos] && Actor.findChar( pos ) == null) {
+
+ Wraith w = new Wraith();
+ w.adjustStats( Dungeon.depth );
+ w.pos = pos;
+ w.state = State.HUNTING;
+ GameScene.add( w, SPAWN_DELAY );
+
+ w.sprite.alpha( 0 );
+ w.sprite.parent.add( new AlphaTweener( w.sprite, 1, 0.5f ) );
+
+ w.sprite.emitter().burst( ShadowParticle.CURSE, 5 );
+
+ return w;
+ } else {
+ return null;
+ }
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Death.class );
+ IMMUNITIES.add( Terror.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/Yog.java b/src/com/watabou/pixeldungeon/actors/mobs/Yog.java
new file mode 100644
index 0000000000..e8a2dad05b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/Yog.java
@@ -0,0 +1,430 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.Fire;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Amok;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Charm;
+import com.watabou.pixeldungeon.actors.buffs.Ooze;
+import com.watabou.pixeldungeon.actors.buffs.Poison;
+import com.watabou.pixeldungeon.actors.buffs.Sleep;
+import com.watabou.pixeldungeon.actors.buffs.Terror;
+import com.watabou.pixeldungeon.effects.Pushing;
+import com.watabou.pixeldungeon.effects.particles.ShadowParticle;
+import com.watabou.pixeldungeon.items.keys.SkeletonKey;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfPsionicBlast;
+import com.watabou.pixeldungeon.items.weapon.enchantments.Death;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.BurningFistSprite;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.LarvaSprite;
+import com.watabou.pixeldungeon.sprites.RottingFistSprite;
+import com.watabou.pixeldungeon.sprites.YogSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Random;
+
+public class Yog extends Mob {
+
+ {
+ name = "Yog-Dzewa";
+ spriteClass = YogSprite.class;
+
+ HP = HT = 300;
+
+ EXP = 50;
+
+ state = State.PASSIVE;
+ }
+
+ private static final String TXT_DESC =
+ "Yog-Dzewa is an Old God, a powerful entity from the realms of chaos. A century ago, the ancient dwarves " +
+ "barely won the war against its army of demons, but were unable to kill the god itself. Instead, they then " +
+ "imprisoned it in the halls below their city, believing it to be too weak to rise ever again.";
+
+ private static int fistsCount = 0;
+
+ public Yog() {
+ super();
+ }
+
+ public void spawnFists() {
+ RottingFist fist1 = new RottingFist();
+ BurningFist fist2 = new BurningFist();
+
+ do {
+ fist1.pos = pos + Level.NEIGHBOURS8[Random.Int( 8 )];
+ fist2.pos = pos + Level.NEIGHBOURS8[Random.Int( 8 )];
+ } while (!Level.passable[fist1.pos] || !Level.passable[fist2.pos] || fist1.pos == fist2.pos);
+
+ GameScene.add( fist1 );
+ GameScene.add( fist2 );
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+
+ if (fistsCount > 0) {
+
+ for (Mob mob : Dungeon.level.mobs) {
+ if (mob instanceof BurningFist || mob instanceof RottingFist) {
+ mob.beckon( pos );
+ }
+ }
+
+ dmg >>= fistsCount;
+ }
+
+ super.damage( dmg, src );
+ }
+
+ @Override
+ public int defenseProc( Char enemy, int damage ) {
+
+ ArrayList spawnPoints = new ArrayList();
+
+ for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
+ int p = pos + Level.NEIGHBOURS8[i];
+ if (Actor.findChar( p ) == null && (Level.passable[p] || Level.avoid[p])) {
+ spawnPoints.add( p );
+ }
+ }
+
+ if (spawnPoints.size() > 0) {
+ Larva larva = new Larva();
+ larva.pos = Random.element( spawnPoints );
+
+ GameScene.add( larva );
+ Actor.addDelayed( new Pushing( larva, pos, larva.pos ), -1 );
+ }
+
+ return super.defenseProc(enemy, damage);
+ }
+
+ @Override
+ public void beckon( int cell ) {
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void die( Object cause ) {
+
+ for (Mob mob : (Iterable)Dungeon.level.mobs.clone()) {
+ if (mob instanceof BurningFist || mob instanceof RottingFist) {
+ mob.die( cause );
+ }
+ }
+
+ GameScene.bossSlain();
+ Dungeon.level.drop( new SkeletonKey( Dungeon.depth ), pos ).sprite.drop();
+ super.die( cause );
+
+ yell( "..." );
+ }
+
+ @Override
+ public void notice() {
+ super.notice();
+ yell( "Hope is an illusion..." );
+ }
+
+ @Override
+ public String description() {
+ return TXT_DESC;
+
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+
+ IMMUNITIES.add( Death.class );
+ IMMUNITIES.add( Terror.class );
+ IMMUNITIES.add( Amok.class );
+ IMMUNITIES.add( Charm.class );
+ IMMUNITIES.add( Sleep.class );
+ IMMUNITIES.add( Burning.class );
+ IMMUNITIES.add( ToxicGas.class );
+ IMMUNITIES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+
+ public static class RottingFist extends Mob {
+
+ private static final int REGENERATION = 4;
+
+ {
+ name = "rotting fist";
+ spriteClass = RottingFistSprite.class;
+
+ HP = HT = 300;
+ defenseSkill = 25;
+
+ EXP = 0;
+
+ state = State.WANDERING;
+ }
+
+ public RottingFist() {
+ super();
+ fistsCount++;
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ fistsCount--;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 36;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 24, 36 );
+ }
+
+ @Override
+ public int dr() {
+ return 15;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ if (Random.Int( 3 ) == 0) {
+ Buff.affect( enemy, Ooze.class );
+ enemy.sprite.burst( 0xFF000000, 5 );
+ }
+
+ return damage;
+ }
+
+ @Override
+ public boolean act() {
+
+ if (Level.water[pos] && HP < HT) {
+ sprite.emitter().burst( ShadowParticle.UP, 2 );
+ HP += REGENERATION;
+ }
+
+ return super.act();
+ }
+
+ @Override
+ public String description() {
+ return TXT_DESC;
+
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( ToxicGas.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Amok.class );
+ IMMUNITIES.add( Sleep.class );
+ IMMUNITIES.add( Terror.class );
+ IMMUNITIES.add( Poison.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+ }
+
+ public static class BurningFist extends Mob {
+
+ {
+ name = "burning fist";
+ spriteClass = BurningFistSprite.class;
+
+ HP = HT = 200;
+ defenseSkill = 25;
+
+ EXP = 0;
+
+ state = State.WANDERING;
+ }
+
+ public BurningFist() {
+ super();
+ fistsCount++;
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+ fistsCount--;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 36;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 20, 32 );
+ }
+
+ @Override
+ public int dr() {
+ return 15;
+ }
+
+ @Override
+ protected boolean canAttack( Char enemy ) {
+ return Ballistica.cast( pos, enemy.pos, false, true ) == enemy.pos;
+ }
+
+ @Override
+ public boolean attack( Char enemy ) {
+
+ if (!Level.adjacent( pos, enemy.pos )) {
+ spend( attackDelay() );
+
+ if (hit( this, enemy, true )) {
+
+ int dmg = damageRoll();
+ enemy.damage( dmg, this );
+
+ enemy.sprite.bloodBurstA( sprite.center(), dmg );
+ enemy.sprite.flash();
+
+ if (!enemy.isAlive() && enemy == Dungeon.hero) {
+ Dungeon.fail( Utils.format( ResultDescriptions.BOSS, name, Dungeon.depth ) );
+ GLog.n( TXT_KILL, name );
+ }
+ return true;
+
+ } else {
+
+ enemy.sprite.showStatus( CharSprite.NEUTRAL, enemy.defenseVerb() );
+ return false;
+ }
+ } else {
+ return super.attack( enemy );
+ }
+ }
+
+ @Override
+ public boolean act() {
+
+ for (int i=0; i < Level.NEIGHBOURS9.length; i++) {
+ GameScene.add( Blob.seed( pos + Level.NEIGHBOURS9[i], 2, Fire.class ) );
+ }
+
+ return super.act();
+ }
+
+ @Override
+ public String description() {
+ return TXT_DESC;
+
+ }
+
+ private static final HashSet> RESISTANCES = new HashSet>();
+ static {
+ RESISTANCES.add( ToxicGas.class );
+ RESISTANCES.add( Death.class );
+ RESISTANCES.add( ScrollOfPsionicBlast.class );
+ }
+
+ @Override
+ public HashSet> resistances() {
+ return RESISTANCES;
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Amok.class );
+ IMMUNITIES.add( Sleep.class );
+ IMMUNITIES.add( Terror.class );
+ IMMUNITIES.add( Burning.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+ }
+
+ public static class Larva extends Mob {
+
+ {
+ name = "god's larva";
+ spriteClass = LarvaSprite.class;
+
+ HP = HT = 25;
+ defenseSkill = 20;
+
+ EXP = 0;
+
+ state = State.HUNTING;
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 30;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 15, 20 );
+ }
+
+ @Override
+ public int dr() {
+ return 8;
+ }
+
+ @Override
+ public String description() {
+ return TXT_DESC;
+
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/Blacksmith.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Blacksmith.java
new file mode 100644
index 0000000000..d10529a14a
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Blacksmith.java
@@ -0,0 +1,326 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import java.util.Collection;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.items.EquipableItem;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.quest.DarkGold;
+import com.watabou.pixeldungeon.items.quest.Pickaxe;
+import com.watabou.pixeldungeon.items.scrolls.ScrollOfUpgrade;
+import com.watabou.pixeldungeon.levels.Room;
+import com.watabou.pixeldungeon.levels.Room.Type;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.BlacksmithSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.windows.WndBlacksmith;
+import com.watabou.pixeldungeon.windows.WndQuest;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Blacksmith extends Mob.NPC {
+
+ private static final String TXT_GOLD_1 =
+ "Hey human! Wanna be useful, eh? Take dis pickaxe and mine me some _dark gold ore_, _15 pieces_ should be enough. " +
+ "What do you mean, how am I gonna pay? You greedy...\n" +
+ "Ok, ok, I don't have money to pay, but I can do some smithin' for you. Consider yourself lucky, " +
+ "I'm the only blacksmith around.";
+ private static final String TXT_BLOOD_1 =
+ "Hey human! Wanna be useful, eh? Take dis pickaxe and _kill a bat_ wit' it, I need its blood on the head. " +
+ "What do you mean, how am I gonna pay? You greedy...\n" +
+ "Ok, ok, I don't have money to pay, but I can do some smithin' for you. Consider yourself lucky, " +
+ "I'm the only blacksmith around.";
+ private static final String TXT2 =
+ "Are you kiddin' me? Where is my pickaxe?!";
+ private static final String TXT3 =
+ "Dark gold ore. 15 pieces. Seriously, is it dat hard?";
+ private static final String TXT4 =
+ "I said I need bat blood on the pickaxe. Chop chop!";
+ private static final String TXT_COMPLETED =
+ "Oh, you have returned... Better late dan never.";
+ private static final String TXT_GET_LOST =
+ "I'm busy. Get lost!";
+
+ private static final String TXT_LOOKS_BETTER = "your %s certainly looks better now";
+
+ {
+ name = "troll blacksmith";
+ spriteClass = BlacksmithSprite.class;
+ }
+
+ @Override
+ protected boolean act() {
+ throwItem();
+ return super.act();
+ }
+
+ @Override
+ public void interact() {
+
+ sprite.turnTo( pos, Dungeon.hero.pos );
+
+ if (!Quest.given) {
+
+ GameScene.show( new WndQuest( this,
+ Quest.alternative ? TXT_BLOOD_1 : TXT_GOLD_1 ) {
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+
+ Quest.given = true;
+ Quest.completed = false;
+
+ Pickaxe pick = new Pickaxe();
+ if (pick.doPickUp( Dungeon.hero )) {
+ GLog.i( Hero.TXT_YOU_NOW_HAVE, pick.name() );
+ } else {
+ Dungeon.level.drop( pick, Dungeon.hero.pos ).sprite.drop();
+ }
+ };
+ } );
+
+ Journal.add( Journal.Feature.TROLL );
+
+ } else if (!Quest.completed) {
+ if (Quest.alternative) {
+
+ Pickaxe pick = Dungeon.hero.belongings.getItem( Pickaxe.class );
+ if (pick == null) {
+ tell( TXT2 );
+ } else if (!pick.bloodStained) {
+ tell( TXT4 );
+ } else {
+ if (pick.isEquipped( Dungeon.hero )) {
+ pick.doUnequip( Dungeon.hero, false );
+ }
+ pick.detach( Dungeon.hero.belongings.backpack );
+ tell( TXT_COMPLETED );
+
+ Quest.completed = true;
+ Quest.reforged = false;
+ }
+
+ } else {
+
+ Pickaxe pick = Dungeon.hero.belongings.getItem( Pickaxe.class );
+ DarkGold gold = Dungeon.hero.belongings.getItem( DarkGold.class );
+ if (pick == null) {
+ tell( TXT2 );
+ } else if (gold == null || gold.quantity() < 15) {
+ tell( TXT3 );
+ } else {
+ if (pick.isEquipped( Dungeon.hero )) {
+ pick.doUnequip( Dungeon.hero, false );
+ }
+ pick.detach( Dungeon.hero.belongings.backpack );
+ gold.detachAll( Dungeon.hero.belongings.backpack );
+ tell( TXT_COMPLETED );
+
+ Quest.completed = true;
+ Quest.reforged = false;
+ }
+
+ }
+ } else if (!Quest.reforged) {
+
+ GameScene.show( new WndBlacksmith( this, Dungeon.hero ) );
+
+ } else {
+
+ tell( TXT_GET_LOST );
+
+ }
+ }
+
+ private void tell( String text ) {
+ GameScene.show( new WndQuest( this, text ) );
+ }
+
+ public static String verify( Item item1, Item item2 ) {
+
+ if (item1 == item2) {
+ return "Select 2 different items, not the same item twice!";
+ }
+
+ if (!item1.isSimilar( item2 )) {
+ return "Select 2 items of the same type!";
+ }
+
+ if (!item1.isIdentified() || !item2.isIdentified()) {
+ return "I need to know what I'm working with, identify them first!";
+ }
+
+ if (item1.cursed || item2.cursed) {
+ return "I don't work with cursed items!";
+ }
+
+ if (item1.level < 0 || item2.level < 0) {
+ return "It's a junk, the quality is too poor!";
+ }
+
+ if (!item1.isUpgradable() || !item2.isUpgradable()) {
+ return "I can't reforge these items!";
+ }
+
+ return null;
+ }
+
+ public static void upgrade( Item item1, Item item2 ) {
+
+ Item first, second;
+ if (item2.level > item1.level) {
+ first = item2;
+ second = item1;
+ } else {
+ first = item1;
+ second = item2;
+ }
+
+ Sample.INSTANCE.play( Assets.SND_EVOKE );
+ ScrollOfUpgrade.upgrade( Dungeon.hero );
+ Item.evoke( Dungeon.hero );
+
+ if (first.isEquipped( Dungeon.hero )) {
+ ((EquipableItem)first).doUnequip( Dungeon.hero, true );
+ }
+ first.upgrade();
+ GLog.p( TXT_LOOKS_BETTER, first.name() );
+ Dungeon.hero.spendAndNext( 2f );
+ Badges.validateItemLevelAquired( first );
+
+ if (second.isEquipped( Dungeon.hero )) {
+ ((EquipableItem)second).doUnequip( Dungeon.hero, false );
+ }
+ second.detachAll( Dungeon.hero.belongings.backpack );
+
+ Quest.reforged = true;
+
+ Journal.remove( Journal.Feature.TROLL );
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+ return 1000;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return
+ "This troll blacksmith looks like all trolls look: he is tall and lean, and his skin resembles stone " +
+ "in both color and texture. The troll blacksmith is tinkering with unproportionally small tools.";
+ }
+
+ public static class Quest {
+
+ private static boolean spawned;
+
+ private static boolean alternative;
+ private static boolean given;
+ private static boolean completed;
+ private static boolean reforged;
+
+ public static void reset() {
+ spawned = false;
+ given = false;
+ completed = false;
+ reforged = false;
+ }
+
+ private static final String NODE = "blacksmith";
+
+ private static final String SPAWNED = "spawned";
+ private static final String ALTERNATIVE = "alternative";
+ private static final String GIVEN = "given";
+ private static final String COMPLETED = "completed";
+ private static final String REFORGED = "reforged";
+
+ public static void storeInBundle( Bundle bundle ) {
+
+ Bundle node = new Bundle();
+
+ node.put( SPAWNED, spawned );
+
+ if (spawned) {
+ node.put( ALTERNATIVE, alternative );
+ node.put( GIVEN, given );
+ node.put( COMPLETED, completed );
+ node.put( REFORGED, reforged );
+ }
+
+ bundle.put( NODE, node );
+ }
+
+ public static void restoreFromBundle( Bundle bundle ) {
+
+ Bundle node = bundle.getBundle( NODE );
+
+ if (!node.isNull() && (spawned = node.getBoolean( SPAWNED ))) {
+ alternative = node.getBoolean( ALTERNATIVE );
+ given = node.getBoolean( GIVEN );
+ completed = node.getBoolean( COMPLETED );
+ reforged = node.getBoolean( REFORGED );
+ } else {
+ reset();
+ }
+ }
+
+ public static void spawn( Collection rooms ) {
+ if (!spawned && Dungeon.depth > 11 && Random.Int( 15 - Dungeon.depth ) == 0) {
+
+ Room blacksmith = null;
+ for (Room r : rooms) {
+ if (r.type == Type.STANDARD && r.width() > 4 && r.height() > 4) {
+ blacksmith = r;
+ blacksmith.type = Type.BLACKSMITH;
+
+ spawned = true;
+ alternative = Random.Int( 2 ) == 0;
+
+ given = false;
+
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/Ghost.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Ghost.java
new file mode 100644
index 0000000000..7af8f3a099
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Ghost.java
@@ -0,0 +1,395 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import java.util.HashSet;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.ParalyticGas;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.quest.DriedRose;
+import com.watabou.pixeldungeon.items.quest.RatSkull;
+import com.watabou.pixeldungeon.items.weapon.Weapon;
+import com.watabou.pixeldungeon.items.weapon.missiles.MissileWeapon;
+import com.watabou.pixeldungeon.levels.SewerLevel;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.FetidRatSprite;
+import com.watabou.pixeldungeon.sprites.GhostSprite;
+import com.watabou.pixeldungeon.windows.WndQuest;
+import com.watabou.pixeldungeon.windows.WndSadGhost;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Ghost extends Mob.NPC {
+
+ {
+ name = "sad ghost";
+ spriteClass = GhostSprite.class;
+
+ flying = true;
+
+ state = State.WANDERING;
+ }
+
+ private static final String TXT_ROSE1 =
+ "Hello adventurer... Once I was like you - strong and confident... " +
+ "And now I'm dead... But I can't leave this place... Not until I have my _dried rose_... " +
+ "It's very important to me... Some monster stole it from my body...";
+
+ private static final String TXT_ROSE2 =
+ "Please... Help me... Find the rose...";
+
+ private static final String TXT_RAT1 =
+ "Hello adventurer... Once I was like you - strong and confident... " +
+ "And now I'm dead... But I can't leave this place... Not until I have my revenge... " +
+ "Slay the _fetid rat_, that has taken my life...";
+
+ private static final String TXT_RAT2 =
+ "Please... Help me... Slay the abomination...";
+
+
+ public Ghost() {
+ super();
+
+ Sample.INSTANCE.load( Assets.SND_GHOST );
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+ return 1000;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "evaded";
+ }
+
+ @Override
+ public float speed() {
+ return 0.5f;
+ }
+
+ @Override
+ protected Char chooseEnemy() {
+ return DUMMY;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ public void interact() {
+ sprite.turnTo( pos, Dungeon.hero.pos );
+
+ Sample.INSTANCE.play( Assets.SND_GHOST );
+
+ if (Quest.given) {
+
+ Item item = Quest.alternative ?
+ Dungeon.hero.belongings.getItem( RatSkull.class ) :
+ Dungeon.hero.belongings.getItem( DriedRose.class );
+ if (item != null) {
+ GameScene.show( new WndSadGhost( this, item ) );
+ } else {
+ GameScene.show( new WndQuest( this, Quest.alternative ? TXT_RAT2 : TXT_ROSE2 ) );
+
+ int newPos = -1;
+ for (int i=0; i < 10; i++) {
+ newPos = Dungeon.level.randomRespawnCell();
+ if (newPos != -1) {
+ break;
+ }
+ }
+ if (newPos != -1) {
+
+ Actor.freeCell( pos );
+
+ CellEmitter.get( pos ).start( Speck.factory( Speck.LIGHT ), 0.2f, 3 );
+ pos = newPos;
+ sprite.place( pos );
+ sprite.visible = Dungeon.visible[pos];
+ }
+ }
+
+ } else {
+ GameScene.show( new WndQuest( this, Quest.alternative ? TXT_RAT1 : TXT_ROSE1 ) );
+ Quest.given = true;
+
+ Journal.add( Journal.Feature.GHOST );
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "The ghost is barely visible. It looks like a shapeless " +
+ "spot of faint light with a sorrowful face.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Paralysis.class );
+ IMMUNITIES.add( Roots.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+
+ public static class Quest {
+
+ private static boolean spawned;
+
+ private static boolean alternative;
+
+ private static boolean given;
+
+ private static boolean processed;
+
+ private static int depth;
+
+ private static int left2kill;
+
+ public static Weapon weapon;
+ public static Armor armor;
+
+ public static void reset() {
+ spawned = false;
+
+ weapon = null;
+ armor = null;
+ }
+
+ private static final String NODE = "sadGhost";
+
+ private static final String SPAWNED = "spawned";
+ private static final String ALTERNATIVE = "alternative";
+ private static final String LEFT2KILL = "left2kill";
+ private static final String GIVEN = "given";
+ private static final String PROCESSED = "processed";
+ private static final String DEPTH = "depth";
+ private static final String WEAPON = "weapon";
+ private static final String ARMOR = "armor";
+
+ public static void storeInBundle( Bundle bundle ) {
+
+ Bundle node = new Bundle();
+
+ node.put( SPAWNED, spawned );
+
+ if (spawned) {
+
+ node.put( ALTERNATIVE, alternative );
+ if (!alternative) {
+ node.put( LEFT2KILL, left2kill );
+ }
+
+ node.put( GIVEN, given );
+ node.put( DEPTH, depth );
+ node.put( PROCESSED, processed );
+
+ node.put( WEAPON, weapon );
+ node.put( ARMOR, armor );
+ }
+
+ bundle.put( NODE, node );
+ }
+
+ public static void restoreFromBundle( Bundle bundle ) {
+
+ Bundle node = bundle.getBundle( NODE );
+
+ if (!node.isNull() && (spawned = node.getBoolean( SPAWNED ))) {
+
+ alternative = node.getBoolean( ALTERNATIVE );
+ if (!alternative) {
+ left2kill = node.getInt( LEFT2KILL );
+ }
+
+ given = node.getBoolean( GIVEN );
+ depth = node.getInt( DEPTH );
+ processed = node.getBoolean( PROCESSED );
+
+ weapon = (Weapon)node.get( WEAPON );
+ armor = (Armor)node.get( ARMOR );
+ } else {
+ reset();
+ }
+ }
+
+ public static void spawn( SewerLevel level ) {
+ if (!spawned && Dungeon.depth > 1 && Random.Int( 5 - Dungeon.depth ) == 0) {
+
+ Ghost ghost = new Ghost();
+ do {
+ ghost.pos = level.randomRespawnCell();
+ } while (ghost.pos == -1);
+ level.mobs.add( ghost );
+ Actor.occupyCell( ghost );
+
+ spawned = true;
+ alternative = Random.Int( 2 ) == 0;
+ if (!alternative) {
+ left2kill = 8;
+ }
+
+ given = false;
+ processed = false;
+ depth = Dungeon.depth;
+
+ do {
+ weapon = (Weapon)Generator.random( Generator.Category.WEAPON );
+ } while (weapon instanceof MissileWeapon);
+ armor = (Armor)Generator.random( Generator.Category.ARMOR );
+
+ for (int i=0; i < 3; i++) {
+ Item another;
+ do {
+ another = Generator.random( Generator.Category.WEAPON );
+ } while (another instanceof MissileWeapon);
+ if (another.level > weapon.level) {
+ weapon = (Weapon)another;
+ }
+ another = Generator.random( Generator.Category.ARMOR );
+ if (another.level > armor.level) {
+ armor = (Armor)another;
+ }
+ }
+ weapon.identify();
+ armor.identify();
+ }
+ }
+
+ public static void process( int pos ) {
+ if (spawned && given && !processed && (depth == Dungeon.depth)) {
+ if (alternative) {
+
+ FetidRat rat = new FetidRat();
+ rat.state = Mob.State.WANDERING;
+ rat.pos = Dungeon.level.randomRespawnCell();
+ if (rat.pos != -1) {
+ GameScene.add( rat );
+ processed = true;
+ }
+
+ } else {
+
+ if (Random.Int( left2kill ) == 0) {
+ Dungeon.level.drop( new DriedRose(), pos ).sprite.drop();
+ processed = true;
+ } else {
+ left2kill--;
+ }
+
+ }
+ }
+ }
+
+ public static void complete() {
+ weapon = null;
+ armor = null;
+
+ Journal.remove( Journal.Feature.GHOST );
+ }
+ }
+
+ public static class FetidRat extends Mob {
+
+ {
+ name = "fetid rat";
+ spriteClass = FetidRatSprite.class;
+
+ HP = HT = 15;
+ defenseSkill = 5;
+
+ EXP = 0;
+ maxLvl = 5;
+ }
+
+ @Override
+ public int damageRoll() {
+ return Random.NormalIntRange( 2, 6 );
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return 12;
+ }
+
+ @Override
+ public int dr() {
+ return 2;
+ }
+
+ @Override
+ public int defenseProc( Char enemy, int damage ) {
+
+ GameScene.add( Blob.seed( pos, 20, ParalyticGas.class ) );
+
+ return super.defenseProc(enemy, damage);
+ }
+
+ @Override
+ public void die( Object cause ) {
+ super.die( cause );
+
+ Dungeon.level.drop( new RatSkull(), pos ).sprite.drop();
+ }
+
+ @Override
+ public String description() {
+ return
+ "This marsupial rat is much larger, than a regular one. It is surrounded by a foul cloud.";
+ }
+
+ private static final HashSet> IMMUNITIES = new HashSet>();
+ static {
+ IMMUNITIES.add( Paralysis.class );
+ }
+
+ @Override
+ public HashSet> immunities() {
+ return IMMUNITIES;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/Imp.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Imp.java
new file mode 100644
index 0000000000..c256246b9c
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Imp.java
@@ -0,0 +1,256 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.mobs.Golem;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.actors.mobs.Monk;
+import com.watabou.pixeldungeon.items.Generator;
+import com.watabou.pixeldungeon.items.quest.DwarfToken;
+import com.watabou.pixeldungeon.items.rings.Ring;
+import com.watabou.pixeldungeon.levels.CityLevel;
+import com.watabou.pixeldungeon.levels.Room;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ImpSprite;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.pixeldungeon.windows.WndImp;
+import com.watabou.pixeldungeon.windows.WndQuest;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Imp extends Mob.NPC {
+
+ {
+ name = "ambitious imp";
+ spriteClass = ImpSprite.class;
+ }
+
+ private static final String TXT_GOLEMS1 =
+ "Are you an adventurer? I love adventurers! You can always rely on them " +
+ "if something needs to be killed. Am I right? For bounty of course ;)\n" +
+ "In my case this is _golems_ who need to be killed. You see, I'm going to start a " +
+ "little business here, but these stupid golems are bad for business! " +
+ "It's very hard to negotiate with wandering lumps of granite, damn them! " +
+ "So please, kill... let's say _6 of them_ and a reward is yours.";
+
+ private static final String TXT_MONKS1 =
+ "Are you an adventurer? I love adventurers! You can always rely on them " +
+ "if something needs to be killed. Am I right? For bounty of course ;)\n" +
+ "In my case this is _monks_ who need to be killed. You see, I'm going to start a " +
+ "little business here, but these lunatics don't buy anything themselves and " +
+ "will scare away other customers. " +
+ "So please, kill... let's say _8 of them_ and a reward is yours.";
+
+ private static final String TXT_GOLEMS2 =
+ "How is your golem safari going?";
+
+ private static final String TXT_MONKS2 =
+ "Oh, you are still alive! I knew that your kung-fu is stronger ;) " +
+ "Just don't forget to grab these monks' tokens.";
+
+ private static final String TXT_CYA = "See you, %s!";
+ private static final String TXT_HEY = "Psst, %s!";
+
+ private boolean seenBefore = false;
+
+ @Override
+ protected boolean act() {
+
+ if (!Quest.given && Dungeon.visible[pos]) {
+ if (!seenBefore) {
+ yell( Utils.format( TXT_HEY, Dungeon.hero.className() ) );
+ }
+ seenBefore = true;
+ } else {
+ seenBefore = false;
+ }
+
+ throwItem();
+
+ return super.act();
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+ return 1000;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "evaded";
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ public void interact() {
+
+ sprite.turnTo( pos, Dungeon.hero.pos );
+ if (Quest.given) {
+
+ DwarfToken tokens = Dungeon.hero.belongings.getItem( DwarfToken.class );
+ if (tokens != null && (tokens.quantity() >= 8 || (!Quest.alternative && tokens.quantity() >= 6))) {
+ GameScene.show( new WndImp( this, tokens ) );
+ } else {
+ tell( Quest.alternative ? TXT_MONKS2 : TXT_GOLEMS2, Dungeon.hero.className() );
+ }
+
+ } else {
+ tell( Quest.alternative ? TXT_MONKS1 : TXT_GOLEMS1 );
+ Quest.given = true;
+ Quest.completed = false;
+
+ Journal.add( Journal.Feature.IMP );
+ }
+ }
+
+ private void tell( String format, Object...args ) {
+ GameScene.show(
+ new WndQuest( this, Utils.format( format, args ) ) );
+ }
+
+ public void flee() {
+
+ yell( Utils.format( TXT_CYA, Dungeon.hero.className() ) );
+
+ destroy();
+ sprite.die();
+ }
+
+ @Override
+ public String description() {
+ return
+ "Imps are lesser demons. They are notable for neither their strength nor their magic talent, " +
+ "but they are quite smart and sociable. Many imps prefer to live among non-demons.";
+ }
+
+ public static class Quest {
+
+ private static boolean alternative;
+
+ private static boolean spawned;
+ private static boolean given;
+ private static boolean completed;
+
+ public static Ring reward;
+
+ public static void reset() {
+ spawned = false;
+
+ reward = null;
+ }
+
+ private static final String NODE = "demon";
+
+ private static final String ALTERNATIVE = "alternative";
+ private static final String SPAWNED = "spawned";
+ private static final String GIVEN = "given";
+ private static final String COMPLETED = "completed";
+ private static final String REWARD = "reward";
+
+ public static void storeInBundle( Bundle bundle ) {
+
+ Bundle node = new Bundle();
+
+ node.put( SPAWNED, spawned );
+
+ if (spawned) {
+ node.put( ALTERNATIVE, alternative );
+
+ node.put( GIVEN, given );
+ node.put( COMPLETED, completed );
+ node.put( REWARD, reward );
+ }
+
+ bundle.put( NODE, node );
+ }
+
+ public static void restoreFromBundle( Bundle bundle ) {
+
+ Bundle node = bundle.getBundle( NODE );
+
+ if (!node.isNull() && (spawned = node.getBoolean( SPAWNED ))) {
+ alternative = node.getBoolean( ALTERNATIVE );
+
+ given = node.getBoolean( GIVEN );
+ completed = node.getBoolean( COMPLETED );
+ reward = (Ring)node.get( REWARD );
+ }
+ }
+
+ public static void spawn( CityLevel level, Room room ) {
+ if (!spawned && Dungeon.depth > 16 && Random.Int( 20 - Dungeon.depth ) == 0) {
+
+ Imp npc = new Imp();
+ do {
+ npc.pos = level.randomRespawnCell();
+ } while (npc.pos == -1 || level.heaps.get( npc.pos ) != null);
+ level.mobs.add( npc );
+ Actor.occupyCell( npc );
+
+ spawned = true;
+ alternative = Random.Int( 2 ) == 0;
+
+ given = false;
+
+ do {
+ reward = (Ring)Generator.random( Generator.Category.RING );
+ } while (reward.cursed);
+ reward.upgrade( 2 );
+ reward.cursed = true;
+ }
+ }
+
+ public static void process( Mob mob ) {
+ if (spawned && given && !completed) {
+ if ((alternative && mob instanceof Monk) ||
+ (!alternative && mob instanceof Golem)) {
+
+ Dungeon.level.drop( new DwarfToken(), mob.pos ).sprite.drop();
+ }
+ }
+ }
+
+ public static void complete() {
+ reward = null;
+ completed = true;
+
+ Journal.remove( Journal.Feature.IMP );
+ }
+
+ public static boolean isCompleted() {
+ return completed;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/ImpShopkeeper.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/ImpShopkeeper.java
new file mode 100644
index 0000000000..eed5255bec
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/ImpShopkeeper.java
@@ -0,0 +1,71 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.effects.particles.ElmoParticle;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.sprites.ImpSprite;
+import com.watabou.pixeldungeon.utils.Utils;
+
+public class ImpShopkeeper extends Shopkeeper {
+
+ private static final String TXT_GREETINGS = "Hello, friend!";
+
+ {
+ name = "ambitious imp";
+ spriteClass = ImpSprite.class;
+ }
+
+ private boolean seenBefore = false;
+
+ @Override
+ protected boolean act() {
+
+ if (!seenBefore && Dungeon.visible[pos]) {
+ yell( Utils.format( TXT_GREETINGS ) );
+ seenBefore = true;
+ }
+
+ return super.act();
+ }
+
+ @Override
+ protected void flee() {
+ for (Heap heap: Dungeon.level.heaps.values()) {
+ if (heap.type == Heap.Type.FOR_SALE) {
+ CellEmitter.get( heap.pos ).burst( ElmoParticle.FACTORY, 4 );
+ heap.destroy();
+ }
+ }
+
+ destroy();
+
+ sprite.emitter().burst( Speck.factory( Speck.WOOL ), 15 );
+ sprite.killAndErase();
+ }
+
+ @Override
+ public String description() {
+ return
+ "Imps are lesser demons. They are notable for neither their strength nor their magic talent. " +
+ "But they are quite smart and sociable, and many of imps prefer to live and do business among non-demons.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/MirrorImage.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/MirrorImage.java
new file mode 100644
index 0000000000..813e036a5b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/MirrorImage.java
@@ -0,0 +1,138 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import java.util.HashSet;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.MirrorSprite;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class MirrorImage extends Mob.NPC {
+
+ {
+ name = "mirror image";
+ spriteClass = MirrorSprite.class;
+
+ state = State.HUNTING;
+
+ enemy = DUMMY;
+ }
+
+ public int tier;
+
+ private int attack;
+ private int damage;
+
+ private static final String TIER = "tier";
+ private static final String ATTACK = "attack";
+ private static final String DAMAGE = "damage";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( TIER, tier );
+ bundle.put( ATTACK, attack );
+ bundle.put( DAMAGE, damage );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ tier = bundle.getInt( TIER );
+ attack = bundle.getInt( ATTACK );
+ damage = bundle.getInt( DAMAGE );
+ }
+
+ public void duplicate( Hero hero ) {
+ tier = hero.tier();
+ attack = hero.attackSkill( hero );
+ damage = hero.damageRoll();
+ }
+
+ @Override
+ public int attackSkill( Char target ) {
+ return attack;
+ }
+
+ @Override
+ public int damageRoll() {
+ return damage;
+ }
+
+ @Override
+ public int attackProc( Char enemy, int damage ) {
+ int dmg = super.attackProc( enemy, damage );
+
+ destroy();
+ sprite.die();
+
+ return dmg;
+ }
+
+ protected Char chooseEnemy() {
+
+ if (enemy == DUMMY || !enemy.isAlive()) {
+ HashSet enemies = new HashSet();
+ for (Mob mob:Dungeon.level.mobs) {
+ if (mob.hostile && Level.fieldOfView[mob.pos]) {
+ enemies.add( mob );
+ }
+ }
+
+ enemy = enemies.size() > 0 ? Random.element( enemies ) : DUMMY;
+ }
+
+ return enemy;
+ }
+
+ @Override
+ public String description() {
+ return
+ "This illusion bears a close resemblance to you, " +
+ "but it's paler and twitches a little.";
+ }
+
+ @Override
+ public CharSprite sprite() {
+ CharSprite s = super.sprite();
+ ((MirrorSprite)s).updateArmor( tier );
+ return s;
+ }
+
+ @Override
+ public void interact() {
+
+ int curPos = pos;
+
+ moveSprite( pos, Dungeon.hero.pos );
+ move( Dungeon.hero.pos );
+
+ Dungeon.hero.sprite.move( Dungeon.hero.pos, curPos );
+ Dungeon.hero.move( curPos );
+
+ Dungeon.hero.spend( 1 / Dungeon.hero.speed() );
+ Dungeon.hero.busy();
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/RatKing.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/RatKing.java
new file mode 100644
index 0000000000..67c4901210
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/RatKing.java
@@ -0,0 +1,82 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+
+import com.watabou.pixeldungeon.sprites.RatKingSprite;
+
+public class RatKing extends Mob.NPC {
+
+ {
+ name = "rat king";
+ spriteClass = RatKingSprite.class;
+
+ state = State.SLEEPING;
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+ return 1000;
+ }
+
+ @Override
+ public float speed() {
+ return 2f;
+ }
+
+ @Override
+ protected Char chooseEnemy() {
+ return DUMMY;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ public void interact() {
+ sprite.turnTo( pos, Dungeon.hero.pos );
+ if (state == State.SLEEPING) {
+ notice();
+ yell( "I'm not sleeping!" );
+ state = State.WANDERING;
+ } else {
+ yell( "What is it? I have no time for this nonsense. My kingdom won't rule itself!" );
+ }
+ }
+
+ @Override
+ public String description() {
+ return
+ "This rat is a little bigger than a regular marsupial rat " +
+ "and it's wearing a tiny crown on its head.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/Shopkeeper.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Shopkeeper.java
new file mode 100644
index 0000000000..363b9b6689
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Shopkeeper.java
@@ -0,0 +1,103 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.particles.ElmoParticle;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ShopkeeperSprite;
+import com.watabou.pixeldungeon.windows.WndBag;
+import com.watabou.pixeldungeon.windows.WndTradeItem;
+
+public class Shopkeeper extends Mob.NPC {
+
+ {
+ name = "shopkeeper";
+ spriteClass = ShopkeeperSprite.class;
+ }
+
+ @Override
+ protected boolean act() {
+
+ throwItem();
+
+ sprite.turnTo( pos, Dungeon.hero.pos );
+ spend( TICK );
+ return true;
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ flee();
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ flee();
+ }
+
+ protected void flee() {
+ for (Heap heap: Dungeon.level.heaps.values()) {
+ if (heap.type == Heap.Type.FOR_SALE) {
+ CellEmitter.get( heap.pos ).burst( ElmoParticle.FACTORY, 4 );
+ heap.destroy();
+ }
+ }
+
+ destroy();
+
+ sprite.killAndErase();
+ CellEmitter.get( pos ).burst( ElmoParticle.FACTORY, 6 );
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ public String description() {
+ return
+ "This stout guy looks more appropriate for a trade district in some large city " +
+ "than for a dungeon. His prices explain why he prefers to do business here.";
+ }
+
+ public static WndBag sell() {
+ return GameScene.selectItem( itemSelector, WndBag.Mode.FOR_SALE, "Select an item to sell" );
+ }
+
+ private static WndBag.Listener itemSelector = new WndBag.Listener() {
+ @Override
+ public void onSelect( Item item ) {
+ if (item != null) {
+ WndBag parentWnd = sell();
+ GameScene.show( new WndTradeItem( item, parentWnd ) );
+ }
+ }
+ };
+
+ @Override
+ public void interact() {
+ sell();
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/actors/mobs/npcs/Wandmaker.java b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Wandmaker.java
new file mode 100644
index 0000000000..9a68d05557
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/actors/mobs/npcs/Wandmaker.java
@@ -0,0 +1,379 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.actors.mobs.npcs;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Journal;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.Heap;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.bags.Bag;
+import com.watabou.pixeldungeon.items.potions.PotionOfStrength;
+import com.watabou.pixeldungeon.items.quest.CorpseDust;
+import com.watabou.pixeldungeon.items.wands.Wand;
+import com.watabou.pixeldungeon.items.wands.WandOfAmok;
+import com.watabou.pixeldungeon.items.wands.WandOfAvalanche;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.items.wands.WandOfDisintegration;
+import com.watabou.pixeldungeon.items.wands.WandOfFirebolt;
+import com.watabou.pixeldungeon.items.wands.WandOfLightning;
+import com.watabou.pixeldungeon.items.wands.WandOfPoison;
+import com.watabou.pixeldungeon.items.wands.WandOfRegrowth;
+import com.watabou.pixeldungeon.items.wands.WandOfSlowness;
+import com.watabou.pixeldungeon.items.wands.WandOfTelekinesis;
+import com.watabou.pixeldungeon.levels.PrisonLevel;
+import com.watabou.pixeldungeon.levels.Room;
+import com.watabou.pixeldungeon.levels.Terrain;
+import com.watabou.pixeldungeon.plants.Plant;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.sprites.WandmakerSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.pixeldungeon.windows.WndQuest;
+import com.watabou.pixeldungeon.windows.WndWandmaker;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Wandmaker extends Mob.NPC {
+
+ {
+ name = "old wandmaker";
+ spriteClass = WandmakerSprite.class;
+ }
+
+ private static final String TXT_BERRY1 =
+ "Oh, what a pleasant surprise to meet a decent person in such place! I came here for a rare ingredient - " +
+ "a _Rotberry seed_. Being a magic user, I'm quite able to defend myself against local monsters, " +
+ "but I'm getting lost in no time, it's very embarrassing. Probably you could help me? I would be " +
+ "happy to pay for your service with one of my best wands.";
+
+ private static final String TXT_DUST1 =
+ "Oh, what a pleasant surprise to meet a decent person in such place! I came here for a rare ingredient - " +
+ "_corpse dust_. It can be gathered from skeletal remains and there is an ample number of them in the dungeon. " +
+ "Being a magic user, I'm quite able to defend myself against local monsters, but I'm getting lost in no time, " +
+ "it's very embarrassing. Probably you could help me? I would be happy to pay for your service with one of my best wands.";
+
+ private static final String TXT_BERRY2 =
+ "Any luck with a Rotberry seed, %s? No? Don't worry, I'm not in a hurry.";
+
+ private static final String TXT_DUST2 =
+ "Any luck with corpse dust, %s? Bone piles are the most obvious places to look.";
+
+ @Override
+ protected boolean act() {
+ throwItem();
+ return super.act();
+ }
+
+ @Override
+ public int defenseSkill( Char enemy ) {
+ return 1000;
+ }
+
+ @Override
+ public String defenseVerb() {
+ return "absorbed";
+ }
+
+ @Override
+ public void damage( int dmg, Object src ) {
+ }
+
+ @Override
+ public void add( Buff buff ) {
+ }
+
+ @Override
+ public boolean reset() {
+ return true;
+ }
+
+ @Override
+ public void interact() {
+
+ sprite.turnTo( pos, Dungeon.hero.pos );
+ if (Quest.given) {
+
+ Item item = Quest.alternative ?
+ Dungeon.hero.belongings.getItem( CorpseDust.class ) :
+ Dungeon.hero.belongings.getItem( Rotberry.Seed.class );
+ if (item != null) {
+ GameScene.show( new WndWandmaker( this, item ) );
+ } else {
+ tell( Quest.alternative ? TXT_DUST2 : TXT_BERRY2, Dungeon.hero.className() );
+ }
+
+ } else {
+ tell( Quest.alternative ? TXT_DUST1 : TXT_BERRY1 );
+ Quest.given = true;
+
+ Quest.placeItem();
+
+ Journal.add( Journal.Feature.WANDMAKER );
+ }
+ }
+
+ private void tell( String format, Object...args ) {
+ GameScene.show( new WndQuest( this, Utils.format( format, args ) ) );
+ }
+
+ @Override
+ public String description() {
+ return
+ "This old but hale gentleman wears a slightly confused " +
+ "expression. He is protected by a magic shield.";
+ }
+
+ public static class Quest {
+
+ private static boolean spawned;
+
+ private static boolean alternative;
+
+ private static boolean given;
+
+ public static Wand wand1;
+ public static Wand wand2;
+
+ public static void reset() {
+ spawned = false;
+
+ wand1 = null;
+ wand2 = null;
+ }
+
+ private static final String NODE = "wandmaker";
+
+ private static final String SPAWNED = "spawned";
+ private static final String ALTERNATIVE = "alternative";
+ private static final String GIVEN = "given";
+ private static final String WAND1 = "wand1";
+ private static final String WAND2 = "wand2";
+
+ public static void storeInBundle( Bundle bundle ) {
+
+ Bundle node = new Bundle();
+
+ node.put( SPAWNED, spawned );
+
+ if (spawned) {
+
+ node.put( ALTERNATIVE, alternative );
+
+ node.put(GIVEN, given );
+
+ node.put( WAND1, wand1 );
+ node.put( WAND2, wand2 );
+ }
+
+ bundle.put( NODE, node );
+ }
+
+ public static void restoreFromBundle( Bundle bundle ) {
+
+ Bundle node = bundle.getBundle( NODE );
+
+ if (!node.isNull() && (spawned = node.getBoolean( SPAWNED ))) {
+
+ alternative = node.getBoolean( ALTERNATIVE );
+
+ given = node.getBoolean( GIVEN );
+
+ wand1 = (Wand)node.get( WAND1 );
+ wand2 = (Wand)node.get( WAND2 );
+ } else {
+ reset();
+ }
+ }
+
+ public static void spawn( PrisonLevel level, Room room ) {
+ if (!spawned && Dungeon.depth > 6 && Random.Int( 10 - Dungeon.depth ) == 0) {
+
+ Wandmaker npc = new Wandmaker();
+ do {
+ npc.pos = room.random();
+ } while (level.map[npc.pos] == Terrain.ENTRANCE || level.map[npc.pos] == Terrain.SIGN);
+ level.mobs.add( npc );
+ Actor.occupyCell( npc );
+
+ spawned = true;
+ alternative = Random.Int( 2 ) == 0;
+
+ given = false;
+
+ switch (Random.Int( 5 )) {
+ case 0:
+ wand1 = new WandOfAvalanche();
+ break;
+ case 1:
+ wand1 = new WandOfDisintegration();
+ break;
+ case 2:
+ wand1 = new WandOfFirebolt();
+ break;
+ case 3:
+ wand1 = new WandOfLightning();
+ break;
+ case 4:
+ wand1 = new WandOfPoison();
+ break;
+ }
+ wand1.random().upgrade();
+
+ switch (Random.Int( 5 )) {
+ case 0:
+ wand2 = new WandOfAmok();
+ break;
+ case 1:
+ wand2 = new WandOfBlink();
+ break;
+ case 2:
+ wand2 = new WandOfRegrowth();
+ break;
+ case 3:
+ wand2 = new WandOfSlowness();
+ break;
+ case 4:
+ wand2 = new WandOfTelekinesis();
+ break;
+ }
+ wand2.random().upgrade();
+ }
+ }
+
+ public static void placeItem() {
+ if (alternative) {
+
+ ArrayList candidates = new ArrayList();
+ for (Heap heap : Dungeon.level.heaps.values()) {
+ if (heap.type == Heap.Type.SKELETON && !Dungeon.visible[heap.pos]) {
+ candidates.add( heap );
+ }
+ }
+
+ if (candidates.size() > 0) {
+ Random.element( candidates ).drop( new CorpseDust() );
+ } else {
+ int pos = Dungeon.level.randomRespawnCell();
+ while (Dungeon.level.heaps.get( pos ) != null) {
+ pos = Dungeon.level.randomRespawnCell();
+ }
+
+ Heap heap = Dungeon.level.drop( new CorpseDust(), pos );
+ heap.type = Heap.Type.SKELETON;
+ heap.sprite.link();
+ }
+
+ } else {
+
+ int shrubPos = Dungeon.level.randomRespawnCell();
+ while (Dungeon.level.heaps.get( shrubPos ) != null) {
+ shrubPos = Dungeon.level.randomRespawnCell();
+ }
+ Dungeon.level.plant( new Rotberry.Seed(), shrubPos );
+
+ }
+ }
+
+ public static void complete() {
+ wand1 = null;
+ wand2 = null;
+
+ Journal.remove( Journal.Feature.WANDMAKER );
+ }
+ }
+
+ public static class Rotberry extends Plant {
+
+ private static final String TXT_DESC =
+ "Berries of this shrub taste like sweet, sweet death.";
+
+ {
+ image = 7;
+ plantName = "Rotberry";
+ }
+
+ @Override
+ public void activate( Char ch ) {
+ super.activate( ch );
+
+ GameScene.add( Blob.seed( pos, 100, ToxicGas.class ) );
+
+ Dungeon.level.drop( new Seed(), pos ).sprite.drop();
+
+ if (ch != null) {
+ Buff.prolong( ch, Roots.class, TICK * 3 );
+ }
+ }
+
+ @Override
+ public String desc() {
+ return TXT_DESC;
+ }
+
+ public static class Seed extends Plant.Seed {
+ {
+ plantName = "Rotberry";
+
+ name = "seed of " + plantName;
+ image = ItemSpriteSheet.SEED_ROTBERRY;
+
+ plantClass = Rotberry.class;
+ alchemyClass = PotionOfStrength.class;
+ }
+
+ @Override
+ public boolean collect( Bag container ) {
+ if (super.collect( container )) {
+
+ if (Dungeon.level != null) {
+ for (Mob mob : Dungeon.level.mobs) {
+ mob.beckon( Dungeon.hero.pos );
+ }
+
+ GLog.w( "The seed emits a roar that echoes throughout the dungeon!" );
+ CellEmitter.center( Dungeon.hero.pos ).start( Speck.factory( Speck.SCREAM ), 0.3f, 3 );
+ Sample.INSTANCE.play( Assets.SND_CHALLENGE );
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String desc() {
+ return TXT_DESC;
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/BadgeBanner.java b/src/com/watabou/pixeldungeon/effects/BadgeBanner.java
new file mode 100644
index 0000000000..2fc334e5dd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/BadgeBanner.java
@@ -0,0 +1,284 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.noosa.TextureFilm;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.utils.PointF;
+
+public class BadgeBanner extends Image {
+
+ private enum State {
+ FADE_IN, STATIC, FADE_OUT
+ };
+ private State state;
+
+ private static final float DEFAULT_SCALE = 3;
+
+ private static final float FADE_IN_TIME = 0.2f;
+ private static final float STATIC_TIME = 1f;
+ private static final float FADE_OUT_TIME = 1.0f;
+
+ private int index;
+ private float time;
+
+ private static TextureFilm atlas;
+
+ private static BadgeBanner current;
+
+ private BadgeBanner( int index ) {
+
+ super( Assets.BADGES );
+
+ if (atlas == null) {
+ atlas = new TextureFilm( texture, 16, 16 );
+ }
+
+ this.index = index;
+
+ frame( atlas.get( index ) );
+ origin.set( width / 2, height / 2 );
+
+ alpha( 0 );
+ scale.set( 2 * DEFAULT_SCALE );
+
+ state = State.FADE_IN;
+ time = FADE_IN_TIME;
+
+ Sample.INSTANCE.play( Assets.SND_BADGE );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ time -= Game.elapsed;
+ if (time >= 0) {
+
+ switch (state) {
+ case FADE_IN:
+ float p = time / FADE_IN_TIME;
+ scale.set( (1 + p) * DEFAULT_SCALE );
+ alpha( 1 - p );
+ break;
+ case STATIC:
+ break;
+ case FADE_OUT:
+ alpha( time / FADE_OUT_TIME );
+ break;
+ }
+
+ } else {
+
+ switch (state) {
+ case FADE_IN:
+ time = STATIC_TIME;
+ state = State.STATIC;
+ scale.set( DEFAULT_SCALE );
+ alpha( 1 );
+ highlight( this, index );
+ break;
+ case STATIC:
+ time = FADE_OUT_TIME;
+ state = State.FADE_OUT;
+ break;
+ case FADE_OUT:
+ killAndErase();
+ break;
+ }
+
+ }
+ }
+
+ @Override
+ public void kill() {
+ if (current == this) {
+ current = null;
+ }
+ super.kill();
+ }
+
+ public static void highlight( Image image, int index ) {
+
+ PointF p = new PointF();
+
+ switch (index) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ p.offset( 7, 3 );
+ break;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ p.offset( 6, 5 );
+ break;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ p.offset( 6, 3 );
+ break;
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ p.offset( 7, 4 );
+ break;
+ case 16:
+ p.offset( 6, 3 );
+ break;
+ case 17:
+ p.offset( 5, 4 );
+ break;
+ case 18:
+ p.offset( 7, 3 );
+ break;
+ case 20:
+ p.offset( 7, 3 );
+ break;
+ case 21:
+ p.offset( 7, 3 );
+ break;
+ case 22:
+ p.offset( 6, 4 );
+ break;
+ case 23:
+ p.offset( 4, 5 );
+ break;
+ case 24:
+ p.offset( 6, 4 );
+ break;
+ case 25:
+ p.offset( 6, 5 );
+ break;
+ case 26:
+ p.offset( 5, 5 );
+ break;
+ case 27:
+ p.offset( 6, 4 );
+ break;
+ case 28:
+ p.offset( 3, 5 );
+ break;
+ case 29:
+ p.offset( 5, 4 );
+ break;
+ case 30:
+ p.offset( 5, 4 );
+ break;
+ case 31:
+ p.offset( 5, 5 );
+ break;
+ case 32:
+ case 33:
+ p.offset( 7, 4 );
+ break;
+ case 34:
+ p.offset( 6, 4 );
+ break;
+ case 35:
+ p.offset( 6, 4 );
+ break;
+ case 36:
+ p.offset( 6, 5 );
+ break;
+ case 37:
+ p.offset( 4, 4 );
+ break;
+ case 38:
+ p.offset( 5, 5 );
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ p.offset( 5, 4 );
+ break;
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ p.offset( 5, 5 );
+ break;
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ p.offset( 7, 4 );
+ break;
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ p.offset( 4, 4 );
+ break;
+ case 56:
+ p.offset( 3, 7 );
+ break;
+ case 57:
+ p.offset( 4, 5 );
+ break;
+ case 58:
+ p.offset( 6, 4 );
+ break;
+ case 59:
+ p.offset( 7, 4 );
+ break;
+ case 60:
+ case 61:
+ case 62:
+ case 63:
+ p.offset( 4, 4 );
+ break;
+ }
+
+ p.x *= image.scale.x;
+ p.y *= image.scale.y;
+ p.offset(
+ -image.origin.x * (image.scale.x - 1),
+ -image.origin.y * (image.scale.y - 1) );
+ p.offset( image.point() );
+
+ Speck star = new Speck();
+ star.reset( 0, p.x, p.y, Speck.DISCOVER );
+ star.camera = image.camera();
+ image.parent.add( star );
+ }
+
+ public static BadgeBanner show( int image ) {
+ if (current != null) {
+ current.killAndErase();
+ }
+ return (current = new BadgeBanner( image ));
+ }
+
+ public static Image image( int index ) {
+ Image image = new Image( Assets.BADGES );
+ if (atlas == null) {
+ atlas = new TextureFilm( image.texture, 16, 16 );
+ }
+ image.frame( atlas.get( index ) );
+ return image;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/BannerSprites.java b/src/com/watabou/pixeldungeon/effects/BannerSprites.java
new file mode 100644
index 0000000000..3d323585ee
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/BannerSprites.java
@@ -0,0 +1,46 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.Assets;
+
+public class BannerSprites {
+
+ public enum Type {
+ PIXEL_DUNGEON,
+ BOSS_SLAIN,
+ GAME_OVER
+ };
+
+ public static Image get( Type type ) {
+ Image icon = new Image( Assets.BANNERS );
+ switch (type) {
+ case PIXEL_DUNGEON:
+ icon.frame( icon.texture.uvRect( 0, 0, 128, 70 ) );
+ break;
+ case BOSS_SLAIN:
+ icon.frame( icon.texture.uvRect( 0, 70, 128, 105 ) );
+ break;
+ case GAME_OVER:
+ icon.frame( icon.texture.uvRect( 0, 105, 128, 140 ) );
+ break;
+ }
+ return icon;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/BlobEmitter.java b/src/com/watabou/pixeldungeon/effects/BlobEmitter.java
new file mode 100644
index 0000000000..2e142195cc
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/BlobEmitter.java
@@ -0,0 +1,59 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.utils.Random;
+
+public class BlobEmitter extends Emitter {
+
+ private static final int WIDTH = Blob.WIDTH;
+ private static final int LENGTH = Blob.LENGTH;
+
+ private Blob blob;
+
+ public BlobEmitter( Blob blob ) {
+
+ super();
+
+ this.blob = blob;
+ blob.use( this );
+ }
+
+ @Override
+ protected void emit( int index ) {
+
+ if (blob.volume <= 0) {
+ return;
+ }
+
+ int[] map = blob.cur;
+ float size = DungeonTilemap.SIZE;
+
+ for (int i=0; i < LENGTH; i++) {
+ if (map[i] > 0 && Dungeon.visible[i]) {
+ float x = ((i % WIDTH) + Random.Float()) * size;
+ float y = ((i / WIDTH) + Random.Float()) * size;
+ factory.emit( this, index, x, y );
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/CellEmitter.java b/src/com/watabou/pixeldungeon/effects/CellEmitter.java
new file mode 100644
index 0000000000..bb2751e290
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/CellEmitter.java
@@ -0,0 +1,56 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.utils.PointF;
+
+public class CellEmitter {
+
+ public static Emitter get( int cell ) {
+
+ PointF p = DungeonTilemap.tileToWorld( cell );
+
+ Emitter emitter = GameScene.emitter();
+ emitter.pos( p.x, p.y, DungeonTilemap.SIZE, DungeonTilemap.SIZE );
+
+ return emitter;
+ }
+
+ public static Emitter center( int cell ) {
+
+ PointF p = DungeonTilemap.tileToWorld( cell );
+
+ Emitter emitter = GameScene.emitter();
+ emitter.pos( p.x + DungeonTilemap.SIZE / 2, p.y + DungeonTilemap.SIZE / 2 );
+
+ return emitter;
+ }
+
+ public static Emitter bottom( int cell ) {
+
+ PointF p = DungeonTilemap.tileToWorld( cell );
+
+ Emitter emitter = GameScene.emitter();
+ emitter.pos( p.x, p.y + DungeonTilemap.SIZE, DungeonTilemap.SIZE, 0 );
+
+ return emitter;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/CheckedCell.java b/src/com/watabou/pixeldungeon/effects/CheckedCell.java
new file mode 100644
index 0000000000..154000b42f
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/CheckedCell.java
@@ -0,0 +1,50 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.gltextures.TextureCache;
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.DungeonTilemap;
+
+public class CheckedCell extends Image {
+
+ private float alpha;
+
+ public CheckedCell( int pos ) {
+ super( TextureCache.createSolid( 0xFF55AAFF ) );
+
+ origin.set( 0.5f );
+
+ point( DungeonTilemap.tileToWorld( pos ).offset(
+ DungeonTilemap.SIZE / 2,
+ DungeonTilemap.SIZE / 2 ) );
+
+ alpha = 0.8f;
+ }
+
+ @Override
+ public void update() {
+ if ((alpha -= Game.elapsed) > 0) {
+ alpha( alpha );
+ scale.set( DungeonTilemap.SIZE * alpha );
+ } else {
+ killAndErase();
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/DeathRay.java b/src/com/watabou/pixeldungeon/effects/DeathRay.java
new file mode 100644
index 0000000000..ba58b8f12f
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/DeathRay.java
@@ -0,0 +1,75 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.utils.PointF;
+
+public class DeathRay extends Image {
+
+ private static final double A = 180 / Math.PI;
+
+ private static final float DURATION = 0.5f;
+
+ private float timeLeft;
+
+ public DeathRay( PointF s, PointF e ) {
+ super( Effects.get( Effects.Type.RAY ) );
+
+ origin.set( 0, height / 2 );
+
+ x = s.x - origin.x;
+ y = s.y - origin.y;
+
+ float dx = e.x - s.x;
+ float dy = e.y - s.y;
+ angle = (float)(Math.atan2( dy, dx ) * A);
+ scale.x = (float)Math.sqrt( dx * dx + dy * dy ) / width;
+
+ Sample.INSTANCE.play( Assets.SND_RAY );
+
+ timeLeft = DURATION;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = timeLeft / DURATION;
+ alpha( p );
+ scale.set( scale.x, p );
+
+ if ((timeLeft -= Game.elapsed) <= 0) {
+ killAndErase();
+ }
+ }
+
+ @Override
+ public void draw() {
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
+ super.draw();
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Effects.java b/src/com/watabou/pixeldungeon/effects/Effects.java
new file mode 100644
index 0000000000..744961ce08
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Effects.java
@@ -0,0 +1,50 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.Assets;
+
+public class Effects {
+
+ public enum Type {
+ RIPPLE,
+ LIGHTNING,
+ WOUND,
+ RAY
+ };
+
+ public static Image get( Type type ) {
+ Image icon = new Image( Assets.EFFECTS );
+ switch (type) {
+ case RIPPLE:
+ icon.frame( icon.texture.uvRect( 0, 0, 16, 16 ) );
+ break;
+ case LIGHTNING:
+ icon.frame( icon.texture.uvRect( 16, 0, 32, 8 ) );
+ break;
+ case WOUND:
+ icon.frame( icon.texture.uvRect( 16, 8, 32, 16 ) );
+ break;
+ case RAY:
+ icon.frame( icon.texture.uvRect( 16, 16, 32, 24 ) );
+ break;
+ }
+ return icon;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/EmoIcon.java b/src/com/watabou/pixeldungeon/effects/EmoIcon.java
new file mode 100644
index 0000000000..f7601c1976
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/EmoIcon.java
@@ -0,0 +1,97 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.ui.Icons;
+import com.watabou.utils.Random;
+
+public class EmoIcon extends Image {
+
+ protected float maxSize = 2;
+ protected float timeScale = 1;
+
+ protected boolean growing = true;
+
+ protected CharSprite owner;
+
+ public EmoIcon( CharSprite owner ) {
+ super();
+
+ this.owner = owner;
+ GameScene.add( this );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if (visible) {
+ if (growing) {
+ scale.set( scale.x + Game.elapsed * timeScale );
+ if (scale.x > maxSize) {
+ growing = false;
+ }
+ } else {
+ scale.set( scale.x - Game.elapsed * timeScale );
+ if (scale.x < 1) {
+ growing = true;
+ }
+ }
+
+ x = owner.x + owner.width - width / 2;
+ y = owner.y - height;
+ }
+ }
+
+ public static class Sleep extends EmoIcon {
+
+ public Sleep( CharSprite owner ) {
+
+ super( owner );
+
+ copy( Icons.get( Icons.SLEEP ) );
+
+ maxSize = 1.2f;
+ timeScale = 0.5f;
+
+ origin.set( width / 2, height / 2 );
+ scale.set( Random.Float( 1, maxSize ) );
+ }
+ }
+
+ public static class Alert extends EmoIcon {
+
+ public Alert( CharSprite owner ) {
+
+ super( owner );
+
+ copy( Icons.get( Icons.ALERT ) );
+
+ maxSize = 1.3f;
+ timeScale = 2;
+
+ origin.set( 2.5f, height - 2.5f );
+ scale.set( Random.Float( 1, maxSize ) );
+ }
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Fireball.java b/src/com/watabou/pixeldungeon/effects/Fireball.java
new file mode 100644
index 0000000000..70b366cbd6
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Fireball.java
@@ -0,0 +1,164 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.graphics.RectF;
+import android.opengl.GLES20;
+
+import com.watabou.glwrap.Texture;
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Group;
+import com.watabou.noosa.Image;
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.ui.Component;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.Random;
+
+public class Fireball extends Component {
+
+ private static final RectF BLIGHT = new RectF( 0, 0, 0.25f, 1 );
+ private static final RectF FLIGHT = new RectF( 0.25f, 0, 0.5f, 1 );
+ private static final RectF FLAME1 = new RectF( 0.50f, 0, 0.75f, 1 );
+ private static final RectF FLAME2 = new RectF( 0.75f, 0, 1.00f, 1 );
+
+ private static final int COLOR = 0xFF66FF;
+
+ private Image bLight;
+ private Image fLight;
+ private Emitter emitter;
+ private Group sparks;
+
+ @Override
+ protected void createChildren() {
+
+ sparks = new Group();
+ add( sparks );
+
+ bLight = new Image( Assets.FIREBALL );
+ bLight.frame( BLIGHT );
+ bLight.origin.set( bLight.width / 2 );
+ bLight.angularSpeed = -90;
+ add( bLight );
+
+ emitter = new Emitter();
+ emitter.pour( new Emitter.Factory() {
+ @Override
+ public void emit(Emitter emitter, int index, float x, float y) {
+ Flame p = (Flame)emitter.recycle( Flame.class );
+ p.reset();
+ p.x = x - p.width / 2;
+ p.y = y - p.height / 2;
+ }
+ }, 0.1f );
+ add( emitter );
+
+ fLight = new Image( Assets.FIREBALL );
+ fLight.frame( FLIGHT );
+ fLight.origin.set( fLight.width / 2 );
+ fLight.angularSpeed = 360;
+ add( fLight );
+
+ bLight.texture.filter( Texture.LINEAR, Texture.LINEAR );
+ }
+
+ @Override
+ protected void layout() {
+
+ bLight.x = x - bLight.width / 2;
+ bLight.y = y - bLight.height / 2;
+
+ emitter.pos(
+ x - bLight.width / 4,
+ y - bLight.height / 4,
+ bLight.width / 2,
+ bLight.height / 2 );
+
+ fLight.x = x - fLight.width / 2;
+ fLight.y = y - fLight.height / 2;
+ }
+
+ @Override
+ public void update() {
+
+ super.update();
+
+ if (Random.Float() < Game.elapsed) {
+ PixelParticle spark = (PixelParticle)sparks.recycle( PixelParticle.Shrinking.class );
+ spark.reset( x, y, ColorMath.random( COLOR, 0x66FF66 ), 2, Random.Float( 0.5f, 1.0f ) );
+ spark.speed.set(
+ Random.Float( -40, +40 ),
+ Random.Float( -60, +20 ) );
+ spark.acc.set( 0, +80 );
+ sparks.add( spark );
+ }
+ }
+
+ @Override
+ public void draw() {
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
+ super.draw();
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
+ }
+
+ public static class Flame extends Image {
+
+ private static float LIFESPAN = 1f;
+
+ private static float SPEED = -40f;
+ private static float ACC = -20f;
+
+ private float timeLeft;
+
+ public Flame() {
+
+ super( Assets.FIREBALL );
+
+ frame( Random.Int( 2 ) == 0 ? FLAME1 : FLAME2 );
+ origin.set( width / 2, height / 2 );
+ acc.set( 0, ACC );
+ }
+
+ public void reset() {
+ revive();
+ timeLeft = LIFESPAN;
+ speed.set( 0, SPEED );
+ }
+
+ @Override
+ public void update() {
+
+ super.update();
+
+ if ((timeLeft -= Game.elapsed) <= 0) {
+
+ kill();
+
+ } else {
+
+ float p = timeLeft / LIFESPAN;
+ scale.set( p );
+ alpha( p > 0.8f ? (1 - p) * 5f : p * 1.25f );
+
+ }
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Flare.java b/src/com/watabou/pixeldungeon/effects/Flare.java
new file mode 100644
index 0000000000..7aab1984b5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Flare.java
@@ -0,0 +1,170 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.annotation.SuppressLint;
+import android.opengl.GLES20;
+import android.util.FloatMath;
+
+import com.watabou.gltextures.Gradient;
+import com.watabou.gltextures.SmartTexture;
+import com.watabou.noosa.Game;
+import com.watabou.noosa.NoosaScript;
+import com.watabou.noosa.Visual;
+
+public class Flare extends Visual {
+
+ private float duration = 0;
+ private float lifespan;
+
+ private boolean lightMode = true;
+
+ private SmartTexture texture;
+
+ private FloatBuffer vertices;
+ private ShortBuffer indices;
+
+ private int nRays;
+
+ @SuppressLint("FloatMath")
+ public Flare( int nRays, float radius ) {
+
+ super( 0, 0, 0, 0 );
+
+ // Texture is incorrectly created every time we need
+ // to show the effect, it must be refactored
+
+ int gradient[] = {0xFFFFFFFF, 0x00FFFFFF};
+ texture = new Gradient( gradient );
+
+ this.nRays = nRays;
+
+ angle = 45;
+ angularSpeed = 180;
+
+ vertices = ByteBuffer.
+ allocateDirect( (nRays * 2 + 1) * 4 * (Float.SIZE / 8) ).
+ order( ByteOrder.nativeOrder() ).
+ asFloatBuffer();
+
+ indices = ByteBuffer.
+ allocateDirect( nRays * 3 * Short.SIZE / 8 ).
+ order( ByteOrder.nativeOrder() ).
+ asShortBuffer();
+
+ float v[] = new float[4];
+
+ v[0] = 0;
+ v[1] = 0;
+ v[2] = 0.25f;
+ v[3] = 0;
+ vertices.put( v );
+
+ v[2] = 0.75f;
+ v[3] = 0;
+
+ for (int i=0; i < nRays; i++) {
+
+ float a = i * 3.1415926f * 2 / nRays;
+ v[0] = FloatMath.cos( a ) * radius;
+ v[1] = FloatMath.sin( a ) * radius;
+ vertices.put( v );
+
+ a += 3.1415926f * 2 / nRays / 2;
+ v[0] = FloatMath.cos( a ) * radius;
+ v[1] = FloatMath.sin( a ) * radius;
+ vertices.put( v );
+
+ indices.put( (short)0 );
+ indices.put( (short)(1 + i * 2) );
+ indices.put( (short)(2 + i * 2) );
+ }
+
+ indices.position( 0 );
+ }
+
+ public Flare color( int color, boolean lightMode ) {
+ this.lightMode = lightMode;
+ hardlight( color );
+
+ return this;
+ }
+
+ public Flare show( Visual visual, float duration ) {
+ point( visual.center() );
+ visual.parent.addToBack( this );
+
+ lifespan = this.duration = duration;
+
+ return this;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if (duration > 0) {
+ if ((lifespan -= Game.elapsed) > 0) {
+
+ float p = 1 - lifespan / duration; // 0 -> 1
+ p = p < 0.25f ? p * 4 : (1 - p) * 1.333f;
+ scale.set( p );
+ alpha( p );
+
+ } else {
+ killAndErase();
+ }
+ }
+ }
+
+ @Override
+ public void draw() {
+
+ super.draw();
+
+ if (lightMode) {
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
+ drawRays();
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
+ } else {
+ drawRays();
+ }
+ }
+
+ private void drawRays() {
+
+ NoosaScript script = NoosaScript.get();
+
+ texture.bind();
+
+ script.uModel.valueM4( matrix );
+ script.lighting(
+ rm, gm, bm, am,
+ ra, ga, ba, aa );
+
+ script.camera( camera );
+ script.drawElements( vertices, indices, nRays * 3 );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/FloatingText.java b/src/com/watabou/pixeldungeon/effects/FloatingText.java
new file mode 100644
index 0000000000..86f05d4487
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/FloatingText.java
@@ -0,0 +1,134 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.BitmapText;
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.scenes.PixelScene;
+import com.watabou.utils.SparseArray;
+
+public class FloatingText extends BitmapText {
+
+ private static final float LIFESPAN = 1f;
+ private static final float DISTANCE = DungeonTilemap.SIZE;
+
+ private float timeLeft;
+
+ private int key = -1;
+
+ private static SparseArray> stacks = new SparseArray>();
+
+ public FloatingText() {
+
+ super();
+
+ PixelScene.chooseFont( 9 );
+ font = PixelScene.font;
+ scale.set( PixelScene.scale );
+
+ speed.y = - DISTANCE / LIFESPAN;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if (timeLeft > 0) {
+ if ((timeLeft -= Game.elapsed) <= 0) {
+ kill();
+ } else {
+ float p = timeLeft / LIFESPAN;
+ alpha( p > 0.5f ? 1 : p * 2 );
+ }
+ }
+ }
+
+ @Override
+ public void kill() {
+ if (key != -1) {
+ stacks.get( key ).remove( this );
+ key = -1;
+ }
+ super.kill();
+ }
+
+ @Override
+ public void destroy() {
+ kill();
+ super.destroy();
+ }
+
+ public void reset( float x, float y, String text, int color ) {
+
+ revive();
+
+ text( text );
+ hardlight( color );
+
+ measure();
+ this.x = PixelScene.align( x - width() / 2 );
+ this.y = y - height();
+
+ timeLeft = LIFESPAN;
+ }
+
+ /* STATIC METHODS */
+
+ public static void show( float x, float y, String text, int color ) {
+ GameScene.status().reset( x, y, text, color );
+ }
+
+ public static void show( float x, float y, int key, String text, int color ) {
+ FloatingText txt = GameScene.status();
+ txt.reset( x, y, text, color );
+ push( txt, key );
+ }
+
+ private static void push( FloatingText txt, int key ) {
+
+ txt.key = key;
+
+ ArrayList stack = stacks.get( key );
+ if (stack == null) {
+ stack = new ArrayList();
+ stacks.put( key, stack );
+ }
+
+ if (stack.size() > 0) {
+ FloatingText below = txt;
+ int aboveIndex = stack.size() - 1;
+ while (aboveIndex >= 0) {
+ FloatingText above = stack.get( aboveIndex );
+ if (above.y + above.height() > below.y) {
+ above.y = below.y - above.height();
+
+ below = above;
+ aboveIndex--;
+ } else {
+ break;
+ }
+ }
+ }
+
+ stack.add( txt );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Halo.java b/src/com/watabou/pixeldungeon/effects/Halo.java
new file mode 100644
index 0000000000..a9f42da1a7
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Halo.java
@@ -0,0 +1,74 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.watabou.gltextures.SmartTexture;
+import com.watabou.gltextures.TextureCache;
+import com.watabou.noosa.Image;
+
+public class Halo extends Image {
+
+ private static final Object CACHE_KEY = Halo.class;
+
+ protected static final int RADIUS = 64;
+
+ protected float radius = RADIUS;
+ protected float brightness = 1;
+
+ public Halo() {
+ super();
+
+ if (!TextureCache.contains( CACHE_KEY )) {
+ Bitmap bmp = Bitmap.createBitmap( RADIUS * 2, RADIUS * 2, Bitmap.Config.ARGB_8888 );
+ Canvas canvas = new Canvas( bmp );
+ Paint paint = new Paint();
+ paint.setColor( 0xFFFFFFFF );
+ canvas.drawCircle( RADIUS, RADIUS, RADIUS * 0.75f, paint );
+ paint.setColor( 0x88FFFFFF );
+ canvas.drawCircle( RADIUS, RADIUS, RADIUS, paint );
+ TextureCache.add( CACHE_KEY, new SmartTexture( bmp ) );
+ }
+
+ texture( CACHE_KEY );
+
+ origin.set( RADIUS );
+ }
+
+ public Halo( float radius, int color, float brightness ) {
+
+ this();
+
+ hardlight( color );
+ alpha( this.brightness = brightness );
+ radius( radius );
+ }
+
+ public Halo point( float x, float y ) {
+ this.x = x - RADIUS;
+ this.y = y - RADIUS;
+ return this;
+ }
+
+ public void radius( float value ) {
+ scale.set( (this.radius = value) / RADIUS );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/IceBlock.java b/src/com/watabou/pixeldungeon/effects/IceBlock.java
new file mode 100644
index 0000000000..24c69b568b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/IceBlock.java
@@ -0,0 +1,68 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Gizmo;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+
+public class IceBlock extends Gizmo {
+
+ private float phase;
+
+ private CharSprite target;
+
+ public IceBlock( CharSprite target ) {
+ super();
+
+ this.target = target;
+ phase = 0;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if ((phase += Game.elapsed * 2) < 1) {
+ target.tint( 0.83f, 1.17f, 1.33f, phase * 0.6f );
+ } else {
+ target.tint( 0.83f, 1.17f, 1.33f, 0.6f );
+ }
+ }
+
+ public void melt() {
+
+ target.resetColor();
+ killAndErase();
+
+ if (visible) {
+ Splash.at( target.center(), 0xFFB2D6FF, 5 );
+ Sample.INSTANCE.play( Assets.SND_SHATTER );
+ }
+ }
+
+ public static IceBlock freeze( CharSprite sprite ) {
+
+ IceBlock iceBlock = new IceBlock( sprite );
+ sprite.parent.add( iceBlock );
+
+ return iceBlock;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Identification.java b/src/com/watabou/pixeldungeon/effects/Identification.java
new file mode 100644
index 0000000000..cb91bcb5dd
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Identification.java
@@ -0,0 +1,101 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+
+import com.watabou.noosa.Group;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class Identification extends Group {
+
+ private static int[] DOTS = {
+ -1, -3,
+ 0, -3,
+ +1, -3,
+ -1, -2,
+ +1, -2,
+ +1, -1,
+ 0, 0,
+ +1, 0,
+ 0, +1,
+ 0, +3
+ };
+
+ public Identification( PointF p ) {
+
+ for (int i=0; i < DOTS.length; i += 2) {
+ add( new Speck( p.x, p.y, DOTS[i], DOTS[i+1] ) );
+ add( new Speck( p.x, p.y, DOTS[i], DOTS[i+1] ) );
+ }
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ if (countLiving() == 0) {
+ killAndErase();
+ }
+ }
+
+ @Override
+ public void draw() {
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
+ super.draw();
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
+ }
+
+ public static class Speck extends PixelParticle {
+
+ public Speck( float x0, float y0, int mx, int my ) {
+
+ super();
+ color( 0x4488CC );
+
+ float x1 = x0 + mx * 3;
+ float y1 = y0 + my * 3;
+
+ PointF p = new PointF().polar( Random.Float( 2 * PointF.PI ), 8 );
+ x0 += p.x;
+ y0 += p.y;
+
+ float dx = x1 - x0;
+ float dy = y1 - y0;
+
+ x = x0;
+ y = y0;
+ speed.set( dx, dy );
+ acc.set( -dx / 4, -dy / 4 );
+
+ left = lifespan = 2f;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ am = 1 - Math.abs( left / lifespan - 0.5f ) * 2;
+ am *= am;
+ size( am * 2 );
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Lightning.java b/src/com/watabou/pixeldungeon/effects/Lightning.java
new file mode 100644
index 0000000000..f1ab3a1259
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Lightning.java
@@ -0,0 +1,142 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Group;
+import com.watabou.noosa.Image;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.utils.Callback;
+import com.watabou.utils.Random;
+
+public class Lightning extends Group {
+
+ private static final float DURATION = 0.3f;
+
+ private float life;
+
+ private int length;
+ private float[] cx;
+ private float[] cy;
+
+ private Image[] arcsS;
+ private Image[] arcsE;
+
+ private Callback callback;
+
+ public Lightning( int[] cells, int length, Callback callback ) {
+
+ super();
+
+ this.callback = callback;
+
+ Image proto = Effects.get( Effects.Type.LIGHTNING );
+ float ox = 0;
+ float oy = proto.height / 2;
+
+ this.length = length;
+ cx = new float[length];
+ cy = new float[length];
+
+ for (int i=0; i < length; i++) {
+ int c = cells[i];
+ cx[i] = (c % Level.WIDTH + 0.5f) * DungeonTilemap.SIZE;
+ cy[i] = (c / Level.WIDTH + 0.5f) * DungeonTilemap.SIZE;
+ }
+
+ arcsS = new Image[length - 1];
+ arcsE = new Image[length - 1];
+ for (int i=0; i < length - 1; i++) {
+
+ Image arc = arcsS[i] = new Image( proto );
+
+ arc.x = cx[i] - arc.origin.x;
+ arc.y = cy[i] - arc.origin.y;
+ arc.origin.set( ox, oy );
+ add( arc );
+
+ arc = arcsE[i] = new Image( proto );
+ arc.origin.set( ox, oy );
+ add( arc );
+ }
+
+ life = DURATION;
+
+ Sample.INSTANCE.play( Assets.SND_LIGHTNING );
+ }
+
+ private static final double A = 180 / Math.PI;
+
+ @Override
+ public void update() {
+ super.update();
+
+ if ((life -= Game.elapsed) < 0) {
+
+ killAndErase();
+ if (callback != null) {
+ callback.call();
+ }
+
+ } else {
+
+ float alpha = life / DURATION;
+
+ for (int i=0; i < length - 1; i++) {
+
+ float sx = cx[i];
+ float sy = cy[i];
+ float ex = cx[i+1];
+ float ey = cy[i+1];
+
+ float x2 = (sx + ex) / 2 + Random.Float( -4, +4 );
+ float y2 = (sy + ey) / 2 + Random.Float( -4, +4 );
+
+ float dx = x2 - sx;
+ float dy = y2 - sy;
+ Image arc = arcsS[i];
+ arc.am = alpha;
+ arc.angle = (float)(Math.atan2( dy, dx ) * A);
+ arc.scale.x = (float)Math.sqrt( dx * dx + dy * dy ) / arc.width;
+
+ dx = ex - x2;
+ dy = ey - y2;
+ arc = arcsE[i];
+ arc.am = alpha;
+ arc.angle = (float)(Math.atan2( dy, dx ) * A);
+ arc.scale.x = (float)Math.sqrt( dx * dx + dy * dy ) / arc.width;
+ arc.x = x2 - arc.origin.x;
+ arc.y = y2 - arc.origin.x;
+ }
+ }
+ }
+
+ @Override
+ public void draw() {
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
+ super.draw();
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/MagicMissile.java b/src/com/watabou/pixeldungeon/effects/MagicMissile.java
new file mode 100644
index 0000000000..9e938140e5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/MagicMissile.java
@@ -0,0 +1,399 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Group;
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.effects.particles.FlameParticle;
+import com.watabou.pixeldungeon.effects.particles.LeafParticle;
+import com.watabou.pixeldungeon.effects.particles.PoisonParticle;
+import com.watabou.pixeldungeon.effects.particles.PurpleParticle;
+import com.watabou.pixeldungeon.effects.particles.ShadowParticle;
+import com.watabou.pixeldungeon.effects.particles.WoolParticle;
+import com.watabou.utils.Callback;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class MagicMissile extends Emitter {
+
+ private static final float SPEED = 200f;
+
+ private Callback callback;
+
+ private float sx;
+ private float sy;
+ private float time;
+
+ public void reset( int from, int to, Callback callback ) {
+ this.callback = callback;
+
+ revive();
+
+ PointF pf = DungeonTilemap.tileCenterToWorld( from );
+ PointF pt = DungeonTilemap.tileCenterToWorld( to );
+
+ x = pf.x;
+ y = pf.y;
+ width = 0;
+ height = 0;
+
+ PointF d = PointF.diff( pt, pf );
+ PointF speed = new PointF( d ).normalize().scale( SPEED );
+ sx = speed.x;
+ sy = speed.y;
+ time = d.length() / SPEED;
+ }
+
+ public void size( float size ) {
+ x -= size / 2;
+ y -= size / 2;
+ width = height = size;
+ }
+
+ public static void blueLight( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.pour( MagicParticle.FACTORY, 0.01f );
+ }
+
+ public static void fire( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 4 );
+ missile.pour( FlameParticle.FACTORY, 0.01f );
+ }
+
+ public static void earth( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 2 );
+ missile.pour( EarthParticle.FACTORY, 0.01f );
+ }
+
+ public static void purpleLight( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 2 );
+ missile.pour( PurpleParticle.MISSILE, 0.01f );
+ }
+
+ public static void whiteLight( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 4 );
+ missile.pour( WhiteParticle.FACTORY, 0.01f );
+ }
+
+ public static void wool( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 3 );
+ missile.pour( WoolParticle.FACTORY, 0.01f );
+ }
+
+ public static void poison( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 3 );
+ missile.pour( PoisonParticle.MISSILE, 0.01f );
+ }
+
+ public static void foliage( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 4 );
+ missile.pour( LeafParticle.GENERAL, 0.01f );
+ }
+
+ public static void slowness( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.pour( SlowParticle.FACTORY, 0.01f );
+ }
+
+ public static void force( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 4 );
+ missile.pour( ForceParticle.FACTORY, 0.01f );
+ }
+
+ public static void coldLight( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 4 );
+ missile.pour( ColdParticle.FACTORY, 0.01f );
+ }
+
+ public static void shadow( Group group, int from, int to, Callback callback ) {
+ MagicMissile missile = ((MagicMissile)group.recycle( MagicMissile.class ));
+ missile.reset( from, to, callback );
+ missile.size( 4 );
+ missile.pour( ShadowParticle.MISSILE, 0.01f );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ if (on) {
+ float d = Game.elapsed;
+ x += sx * d;
+ y += sy * d;
+ if ((time -= d) <= 0) {
+ on = false;
+ callback.call();
+ }
+ }
+ }
+
+ public static class MagicParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((MagicParticle)emitter.recycle( MagicParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public MagicParticle() {
+ super();
+
+ color( 0x88CCFF );
+ lifespan = 0.5f;
+
+ speed.set( Random.Float( -10, +10 ), Random.Float( -10, +10 ) );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ // alpha: 1 -> 0; size: 1 -> 4
+ size( 4 - (am = left / lifespan) * 3 );
+ }
+ }
+
+ public static class EarthParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((EarthParticle)emitter.recycle( EarthParticle.class )).reset( x, y );
+ }
+ };
+
+ public EarthParticle() {
+ super();
+
+ lifespan = 0.5f;
+
+ color( ColorMath.random( 0x555555, 0x777766 ) );
+
+ acc.set( 0, +40 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+ size = 4;
+
+ speed.set( Random.Float( -10, +10 ), Random.Float( -10, +10 ) );
+ }
+ }
+
+ public static class WhiteParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((WhiteParticle)emitter.recycle( WhiteParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public WhiteParticle() {
+ super();
+
+ lifespan = 0.4f;
+
+ am = 0.5f;
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ // size: 3 -> 0
+ size( (left / lifespan) * 3 );
+ }
+ }
+
+ public static class SlowParticle extends PixelParticle {
+
+ private Emitter emitter;
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((SlowParticle)emitter.recycle( SlowParticle.class )).reset( x, y, emitter );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public SlowParticle() {
+ super();
+
+ lifespan = 0.6f;
+
+ color( 0x664422 );
+ size( 2 );
+ }
+
+ public void reset( float x, float y, Emitter emitter ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+ this.emitter = emitter;
+
+ left = lifespan;
+
+ acc.set( 0 );
+ speed.set( Random.Float( -20, +20 ), Random.Float( -20, +20 ) );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ am = left / lifespan;
+ acc.set( (emitter.x - x) * 10, (emitter.y - y) * 10 );
+ }
+ }
+
+ public static class ForceParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ForceParticle)emitter.recycle( ForceParticle.class )).reset( x, y );
+ }
+ };
+
+ public ForceParticle() {
+ super();
+
+ lifespan = 0.6f;
+
+ size( 4 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+
+ acc.set( 0 );
+ speed.set( Random.Float( -40, +40 ), Random.Float( -40, +40 ) );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ am = (left / lifespan) / 2;
+ acc.set( -speed.x * 10, -speed.y * 10 );
+ }
+ }
+
+ public static class ColdParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ColdParticle)emitter.recycle( ColdParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public ColdParticle() {
+ super();
+
+ lifespan = 0.6f;
+
+ color( 0x2244FF );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+ size = 8;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ am = 1 - left / lifespan;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Pushing.java b/src/com/watabou/pixeldungeon/effects/Pushing.java
new file mode 100644
index 0000000000..7dfbb8406d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Pushing.java
@@ -0,0 +1,100 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Visual;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.utils.PointF;
+
+public class Pushing extends Actor {
+
+ private CharSprite sprite;
+ private int from;
+ private int to;
+
+ private Effect effect;
+
+ public Pushing( Char ch, int from, int to ) {
+ sprite = ch.sprite;
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ protected boolean act() {
+ if (sprite != null) {
+
+ if (effect == null) {
+ new Effect();
+ }
+ return false;
+
+ } else {
+
+ Actor.remove( Pushing.this );
+ return true;
+ }
+ }
+
+ public class Effect extends Visual {
+
+ private static final float DELAY = 0.15f;
+
+ private PointF end;
+
+ private float delay;
+
+ public Effect() {
+ super( 0, 0, 0, 0 );
+
+ point( sprite.worldToCamera( from ) );
+ end = sprite.worldToCamera( to );
+
+ speed.set( 2 * (end.x - x) / DELAY, 2 * (end.y - y) / DELAY );
+ acc.set( -speed.x / DELAY, -speed.y / DELAY );
+
+ delay = 0;
+
+ sprite.parent.add( this );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if ((delay += Game.elapsed) < DELAY) {
+
+ sprite.x = x;
+ sprite.y = y;
+
+ } else {
+
+ sprite.point( end );
+
+ killAndErase();
+ Actor.remove( Pushing.this );
+
+ next();
+ }
+ }
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Ripple.java b/src/com/watabou/pixeldungeon/effects/Ripple.java
new file mode 100644
index 0000000000..d45c8df266
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Ripple.java
@@ -0,0 +1,59 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.levels.Level;
+
+public class Ripple extends Image {
+
+ private static final float TIME_TO_FADE = 0.5f;
+
+ private float time;
+
+ public Ripple() {
+ super( Effects.get( Effects.Type.RIPPLE ) );
+ }
+
+ public void reset( int p ) {
+ revive();
+
+ x = (p % Level.WIDTH) * DungeonTilemap.SIZE;
+ y = (p / Level.WIDTH) * DungeonTilemap.SIZE;
+
+ origin.set( width / 2, height / 2 );
+ scale.set( 0 );
+
+ time = TIME_TO_FADE;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if ((time -= Game.elapsed) <= 0) {
+ kill();
+ } else {
+ float p = time / TIME_TO_FADE;
+ scale.set( 1 - p );
+ alpha( p );
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Speck.java b/src/com/watabou/pixeldungeon/effects/Speck.java
new file mode 100644
index 0000000000..0a3e5ffe49
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Speck.java
@@ -0,0 +1,419 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import android.annotation.SuppressLint;
+import android.util.FloatMath;
+import android.util.SparseArray;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.noosa.TextureFilm;
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class Speck extends Image {
+
+ public static final int HEALING = 0;
+ public static final int STAR = 1;
+ public static final int LIGHT = 2;
+ public static final int QUESTION = 3;
+ public static final int UP = 4;
+ public static final int SCREAM = 5;
+ public static final int BONE = 6;
+ public static final int WOOL = 7;
+ public static final int ROCK = 8;
+ public static final int NOTE = 9;
+ public static final int CHANGE = 10;
+ public static final int HEART = 11;
+ public static final int BUBBLE = 12;
+ public static final int STEAM = 13;
+ public static final int COIN = 14;
+
+ public static final int DISCOVER = 101;
+ public static final int EVOKE = 102;
+ public static final int MASTERY = 103;
+ public static final int KIT = 104;
+ public static final int RATTLE = 105;
+ public static final int JET = 106;
+ public static final int TOXIC = 107;
+ public static final int PARALYSIS = 108;
+ public static final int DUST = 109;
+ public static final int FORGE = 110;
+
+ private static final int SIZE = 7;
+
+ private int type;
+ private float lifespan;
+ private float left;
+
+ private static TextureFilm film;
+
+ private static SparseArray factories = new SparseArray();
+
+ public Speck() {
+ super();
+
+ texture( Assets.SPECKS );
+ if (film == null) {
+ film = new TextureFilm( texture, SIZE, SIZE );
+ }
+
+ origin.set( SIZE / 2f );
+ }
+
+ public void reset( int index, float x, float y, int type ) {
+ revive();
+
+ this.type = type;
+ switch (type) {
+ case DISCOVER:
+ frame( film.get( LIGHT ) );
+ break;
+ case EVOKE:
+ case MASTERY:
+ case KIT:
+ case FORGE:
+ frame( film.get( STAR ) );
+ break;
+ case RATTLE:
+ frame( film.get( BONE ) );
+ break;
+ case JET:
+ case TOXIC:
+ case PARALYSIS:
+ case DUST:
+ frame( film.get( STEAM ) );
+ break;
+ default:
+ frame( film.get( type ) );
+ }
+
+ this.x = x - origin.x;
+ this.y = y - origin.y;
+
+ resetColor();
+ scale.set( 1 );
+ speed.set( 0 );
+ acc.set( 0 );
+ angle = 0;
+ angularSpeed = 0;
+
+ switch (type) {
+
+ case HEALING:
+ speed.set( 0, -20 );
+ lifespan = 1f;
+ break;
+
+ case STAR:
+ speed.polar( Random.Float( 2 * 3.1415926f ), Random.Float( 128 ) );
+ acc.set( 0, 128 );
+ angle = Random.Float( 360 );
+ angularSpeed = Random.Float( -360, +360 );
+ lifespan = 1f;
+ break;
+
+ case FORGE:
+ speed.polar( Random.Float( -3.1415926f, 0 ), Random.Float( 64 ) );
+ acc.set( 0, 128 );
+ angle = Random.Float( 360 );
+ angularSpeed = Random.Float( -360, +360 );
+ lifespan = 0.51f;
+ break;
+
+ case EVOKE:
+ speed.polar( Random.Float( -3.1415926f, 0 ), 50 );
+ acc.set( 0, 50 );
+ angle = Random.Float( 360 );
+ angularSpeed = Random.Float( -180, +180 );
+ lifespan = 1f;
+ break;
+
+ case KIT:
+ speed.polar( index * 3.1415926f / 5, 50 );
+ acc.set( -speed.x, -speed.y );
+ angle = index * 36;
+ angularSpeed = 360;
+ lifespan = 1f;
+ break;
+
+ case MASTERY:
+ speed.set( Random.Int( 2 ) == 0 ? Random.Float( -128, -64 ) : Random.Float( +64, +128 ), 0 );
+ angularSpeed = speed.x < 0 ? -180 : +180;
+ acc.set( -speed.x, 0 );
+ lifespan = 0.5f;
+ break;
+
+ case LIGHT:
+ angle = Random.Float( 360 );
+ angularSpeed = 90;
+ lifespan = 1f;
+ break;
+
+ case DISCOVER:
+ angle = Random.Float( 360 );
+ angularSpeed = 90;
+ lifespan = 0.5f;
+ am = 0;
+ break;
+
+ case QUESTION:
+ lifespan = 0.8f;
+ break;
+
+ case UP:
+ speed.set( 0, -20 );
+ lifespan = 1f;
+ break;
+
+ case SCREAM:
+ lifespan = 0.9f;
+ break;
+
+ case BONE:
+ lifespan = 0.2f;
+ speed.polar( Random.Float( 2 * 3.1415926f ), 24 / lifespan );
+ acc.set( 0, 128 );
+ angle = Random.Float( 360 );
+ angularSpeed = 360;
+ break;
+
+ case RATTLE:
+ lifespan = 0.5f;
+ speed.set( 0, -200 );
+ acc.set( 0, -2 * speed.y / lifespan );
+ angle = Random.Float( 360 );
+ angularSpeed = 360;
+ break;
+
+ case WOOL:
+ lifespan = 0.5f;
+ speed.set( 0, -50 );
+ angle = Random.Float( 360 );
+ angularSpeed = Random.Float( -360, +360 );
+ break;
+
+ case ROCK:
+ angle = Random.Float( 360 );
+ angularSpeed = Random.Float( -360, +360 );
+ scale.set( Random.Float( 1, 2 ) );
+ speed.set( 0, 64 );
+ lifespan = 0.2f;
+ y -= speed.y * lifespan;
+ break;
+
+ case NOTE:
+ angularSpeed = Random.Float( -30, +30 );
+ speed.polar( (angularSpeed - 90) * PointF.G2R, 30 );
+ lifespan = 1f;
+ break;
+
+ case CHANGE:
+ angle = Random.Float( 360 );
+ speed.polar( (angle - 90) * PointF.G2R, Random.Float( 4, 12 ) );
+ lifespan = 1.5f;
+ break;
+
+ case HEART:
+ speed.set( Random.Int( -10, +10 ), -40 );
+ angularSpeed = Random.Float( -45, +45 );
+ lifespan = 1f;
+ break;
+
+ case BUBBLE:
+ speed.set( 0, -15 );
+ scale.set( Random.Float( 0.8f, 1 ) );
+ lifespan = Random.Float( 0.8f, 1.5f );
+ break;
+
+ case STEAM:
+ speed.y = -Random.Float( 20, 30 );
+ angularSpeed = Random.Float( +180 );
+ angle = Random.Float( 360 );
+ lifespan = 1f;
+ break;
+
+ case JET:
+ speed.y = +32;
+ acc.y = -64;
+ angularSpeed = Random.Float( 180, 360 );
+ angle = Random.Float( 360 );
+ lifespan = 0.5f;
+ break;
+
+ case TOXIC:
+ hardlight( 0x50FF60 );
+ angularSpeed = 30;
+ angle = Random.Float( 360 );
+ lifespan = Random.Float( 1f, 3f );
+ break;
+
+ case PARALYSIS:
+ hardlight( 0xFFFF66 );
+ angularSpeed = -30;
+ angle = Random.Float( 360 );
+ lifespan = Random.Float( 1f, 3f );
+ break;
+
+ case DUST:
+ hardlight( 0xFFFF66 );
+ angle = Random.Float( 360 );
+ speed.polar( Random.Float( 2 * 3.1415926f ), Random.Float( 16, 48 ) );
+ lifespan = 0.5f;
+ break;
+
+ case COIN:
+ speed.polar( -PointF.PI * Random.Float( 0.3f, 0.7f ), Random.Float( 48, 96 ) );
+ acc.y = 256;
+ lifespan = -speed.y / acc.y * 2;
+ break;
+ }
+
+ left = lifespan;
+ }
+
+ @SuppressLint("FloatMath")
+ @Override
+ public void update() {
+ super.update();
+
+ left -= Game.elapsed;
+ if (left <= 0) {
+
+ kill();
+
+ } else {
+
+ float p = 1 - left / lifespan; // 0 -> 1
+
+ switch (type) {
+
+ case STAR:
+ case FORGE:
+ scale.set( 1 - p );
+ am = p < 0.2f ? p * 5f : (1 - p) * 1.25f;
+ break;
+
+ case KIT:
+ case MASTERY:
+ am = 1 - p * p;
+ break;
+
+ case EVOKE:
+
+ case HEALING:
+ am = p < 0.5f ? 1 : 2 - p * 2;
+ break;
+
+ case LIGHT:
+ am = scale.set( p < 0.2f ? p * 5f : (1 - p) * 1.25f ).x;
+ break;
+
+ case DISCOVER:
+ am = 1 - p;
+ scale.set( (p < 0.5f ? p : 1 - p) * 2 );
+ break;
+
+ case QUESTION:
+ scale.set( (float)(Math.sqrt( p < 0.5f ? p : 1 - p ) * 3) );
+ break;
+
+ case UP:
+ scale.set( (float)(Math.sqrt( p < 0.5f ? p : 1 - p ) * 2) );
+ break;
+
+ case SCREAM:
+ am = (float)Math.sqrt( (p < 0.5f ? p : 1 - p) * 2f );
+ scale.set( p * 7 );
+ break;
+
+ case BONE:
+ case RATTLE:
+ am = p < 0.9f ? 1 : (1 - p) * 10;
+ break;
+
+ case ROCK:
+ am = p < 0.2f ? p * 5 : 1 ;
+ break;
+
+ case NOTE:
+ am = 1 - p * p;
+ break;
+
+ case WOOL:
+ scale.set( 1 - p );
+ break;
+
+ case CHANGE:
+ am = (float)FloatMath.sqrt( (p < 0.5f ? p : 1 - p) * 2);
+ scale.y = (1 + p) * 0.5f;
+ scale.x = scale.y * FloatMath.cos( left * 15 );
+ break;
+
+ case HEART:
+ scale.set( 1 - p );
+ am = 1 - p * p;
+ break;
+
+ case BUBBLE:
+ am = p < 0.2f ? p * 5 : 1;
+ break;
+
+ case STEAM:
+ case TOXIC:
+ case PARALYSIS:
+ case DUST:
+ am = p < 0.5f ? p : 1 - p;
+ scale.set( 1 + p * 2 );
+ break;
+
+ case JET:
+ am = (p < 0.5f ? p : 1 - p) * 2;
+ scale.set( p * 1.5f );
+ break;
+
+ case COIN:
+ scale.x = FloatMath.cos( left * 5 );
+ rm = gm = bm = (Math.abs( scale.x ) + 1) * 0.5f;
+ am = p < 0.9f ? 1 : (1 - p) * 10;
+ break;
+ }
+ }
+ }
+
+ public static Emitter.Factory factory( final int type ) {
+
+ Emitter.Factory factory = factories.get( type );
+
+ if (factory == null) {
+ factory = new Emitter.Factory() {
+ @Override
+ public void emit ( Emitter emitter, int index, float x, float y ) {
+ Speck p = (Speck)emitter.recycle( Speck.class );
+ p.reset( index, x, y, type );
+ }
+ };
+ factories.put( type, factory );
+ }
+
+ return factory;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/SpellSprite.java b/src/com/watabou/pixeldungeon/effects/SpellSprite.java
new file mode 100644
index 0000000000..f27e1786f7
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/SpellSprite.java
@@ -0,0 +1,135 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import java.util.HashMap;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Image;
+import com.watabou.noosa.TextureFilm;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.scenes.GameScene;
+
+public class SpellSprite extends Image {
+
+ public static final int FOOD = 0;
+ public static final int MAP = 1;
+ public static final int CHARGE = 2;
+ public static final int MASTERY = 3;
+
+ private static final int SIZE = 16;
+
+ private enum Phase {
+ FADE_IN, STATIC, FADE_OUT
+ };
+
+ private static final float FADE_IN_TIME = 0.2f;
+ private static final float STATIC_TIME = 0.8f;
+ private static final float FADE_OUT_TIME = 0.4f;
+
+ private static TextureFilm film;
+
+ private Char target;
+
+ private Phase phase;
+ private float duration;
+ private float passed;
+
+ private static HashMap all = new HashMap();
+
+ public SpellSprite() {
+ super( Assets.SPELL_ICONS );
+
+ if (film == null) {
+ film = new TextureFilm( texture, SIZE );
+ }
+ }
+
+ public void reset( int index ) {
+ frame( film.get( index ) );
+ origin.set( width / 2, height / 2 );
+
+ phase = Phase.FADE_IN;
+
+ duration = FADE_IN_TIME;
+ passed = 0;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ x = target.sprite.center().x - SIZE / 2;
+ y = target.sprite.y - SIZE;
+
+ switch (phase) {
+ case FADE_IN:
+ alpha( passed / duration );
+ scale.set( passed / duration );
+ break;
+ case STATIC:
+ break;
+ case FADE_OUT:
+ alpha( 1 - passed / duration );
+ break;
+ }
+
+ if ((passed += Game.elapsed) > duration) {
+ switch (phase) {
+ case FADE_IN:
+ phase = Phase.STATIC;
+ duration = STATIC_TIME;
+ break;
+ case STATIC:
+ phase = Phase.FADE_OUT;
+ duration = FADE_OUT_TIME;
+ break;
+ case FADE_OUT:
+ kill();
+ break;
+ }
+
+ passed = 0;
+ }
+ }
+
+ @Override
+ public void kill() {
+ super.kill();
+ all.remove( target );
+ }
+
+ public static void show( Char ch, int index ) {
+
+ if (!ch.sprite.visible) {
+ return;
+ }
+
+ SpellSprite old = all.get( ch );
+ if (old != null) {
+ old.kill();
+ }
+
+ SpellSprite sprite = GameScene.spellSprite();
+ sprite.revive();
+ sprite.reset( index );
+ sprite.target = ch;
+ all.put( ch, sprite );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Splash.java b/src/com/watabou/pixeldungeon/effects/Splash.java
new file mode 100644
index 0000000000..cf2d9ea821
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Splash.java
@@ -0,0 +1,80 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class Splash {
+
+ public static void at( int cell, final int color, int n ) {
+ at( DungeonTilemap.tileCenterToWorld( cell ), color, n );
+ }
+
+ public static void at( PointF p, final int color, int n ) {
+
+ if (n <= 0) {
+ return;
+ }
+
+ Emitter emitter = GameScene.emitter();
+ emitter.pos( p );
+
+ FACTORY.color = color;
+ FACTORY.dir = -3.1415926f / 2;
+ FACTORY.cone = 3.1415926f;
+ emitter.burst( FACTORY, n );
+ }
+
+ public static void at( PointF p, final float dir, final float cone, final int color, int n ) {
+
+ if (n <= 0) {
+ return;
+ }
+
+ Emitter emitter = GameScene.emitter();
+ emitter.pos( p );
+
+ FACTORY.color = color;
+ FACTORY.dir = dir;
+ FACTORY.cone = cone;
+ emitter.burst( FACTORY, n );
+ }
+
+ private static final SplashFactory FACTORY = new SplashFactory();
+
+ private static class SplashFactory extends Emitter.Factory {
+
+ public int color;
+ public float dir;
+ public float cone;
+
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ PixelParticle p = (PixelParticle)emitter.recycle( PixelParticle.Shrinking.class );
+
+ p.reset( x, y, color, 4, Random.Float( 0.5f, 1.0f ) );
+ p.speed.polar( Random.Float( dir - cone / 2, dir + cone / 2 ), Random.Float( 40, 80 ) );
+ p.acc.set( 0, +100 );
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/TorchHalo.java b/src/com/watabou/pixeldungeon/effects/TorchHalo.java
new file mode 100644
index 0000000000..fe89081e63
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/TorchHalo.java
@@ -0,0 +1,71 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+
+public class TorchHalo extends Halo {
+
+ private CharSprite target;
+
+ private float phase = 0;
+
+ public TorchHalo( CharSprite sprite ) {
+ super( 24, 0xFFDDCC, 0.15f );
+ target = sprite;
+ am = 0;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if (phase < 0) {
+ if ((phase += Game.elapsed) >= 0) {
+ killAndErase();
+ } else {
+ scale.set( (2 + phase) * radius / RADIUS );
+ am = -phase * brightness;
+ }
+ } else if (phase < 1) {
+ if ((phase += Game.elapsed) >= 1) {
+ phase = 1;
+ }
+ scale.set( phase * radius / RADIUS );
+ am = phase * brightness;
+ }
+
+ point( target.x + target.width / 2, target.y + target.height / 2 );
+ }
+
+ @Override
+ public void draw() {
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE );
+ super.draw();
+ GLES20.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
+ }
+
+ public void putOut() {
+ phase = -1;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/Wound.java b/src/com/watabou/pixeldungeon/effects/Wound.java
new file mode 100644
index 0000000000..6e4c60306d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/Wound.java
@@ -0,0 +1,83 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Group;
+import com.watabou.noosa.Image;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.levels.Level;
+
+public class Wound extends Image {
+
+ private static final float TIME_TO_FADE = 0.8f;
+
+ private float time;
+
+ public Wound() {
+ super( Effects.get( Effects.Type.WOUND ) );
+ origin.set( width / 2, height / 2 );
+ }
+
+ public void reset( int p ) {
+ revive();
+
+ x = (p % Level.WIDTH) * DungeonTilemap.SIZE + (DungeonTilemap.SIZE - width) / 2;
+ y = (p / Level.WIDTH) * DungeonTilemap.SIZE + (DungeonTilemap.SIZE - height) / 2;
+
+ time = TIME_TO_FADE;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ if ((time -= Game.elapsed) <= 0) {
+ kill();
+ } else {
+ float p = time / TIME_TO_FADE;
+ alpha( p );
+ scale.x = 1 + p;
+ }
+ }
+
+ public static void hit( Char ch ) {
+ hit( ch, 0 );
+ }
+
+ public static void hit( Char ch, float angle ) {
+ Wound w = (Wound)ch.sprite.parent.recycle( Wound.class );
+ ch.sprite.parent.bringToFront( w );
+ w.reset( ch.pos );
+ w.angle = angle;
+ }
+
+ public static void hit( int pos ) {
+ hit( pos, 0 );
+ }
+
+ public static void hit( int pos, float angle ) {
+ Group parent = Dungeon.hero.sprite.parent;
+ Wound w = (Wound)parent.recycle( Wound.class );
+ parent.bringToFront( w );
+ w.reset( pos );
+ w.angle = angle;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/effects/particles/BloodParticle.java b/src/com/watabou/pixeldungeon/effects/particles/BloodParticle.java
new file mode 100644
index 0000000000..33b91e5c7d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/BloodParticle.java
@@ -0,0 +1,60 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+
+public class BloodParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((BloodParticle)emitter.recycle( BloodParticle.class )).reset( x, y );
+ }
+ };
+
+ public BloodParticle() {
+ super();
+
+ color( 0xCC0000 );
+ lifespan = 0.8f;
+
+ acc.set( 0, +40 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+
+ size = 4;
+ speed.set( 0 );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ float p = left / lifespan;
+ am = p > 0.6f ? (1 - p) * 2.5f : 1;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/EarthParticle.java b/src/com/watabou/pixeldungeon/effects/particles/EarthParticle.java
new file mode 100644
index 0000000000..1d1cb4a706
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/EarthParticle.java
@@ -0,0 +1,60 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.Random;
+
+public class EarthParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((EarthParticle)emitter.recycle( EarthParticle.class )).reset( x, y );
+ }
+ };
+
+ public EarthParticle() {
+ super();
+
+ color( ColorMath.random( 0x444444, 0x777766 ) );
+ angle = Random.Float( -30, 30 );
+
+ lifespan = 0.5f;
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ size( (p < 0.5f ? p : 1 - p) * 16 );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/ElmoParticle.java b/src/com/watabou/pixeldungeon/effects/particles/ElmoParticle.java
new file mode 100644
index 0000000000..d87cf01125
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/ElmoParticle.java
@@ -0,0 +1,64 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+
+public class ElmoParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ElmoParticle)emitter.recycle( ElmoParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public ElmoParticle() {
+ super();
+
+ color( 0x22EE66 );
+ lifespan = 0.6f;
+
+ acc.set( 0, -80 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+
+ size = 4;
+ speed.set( 0 );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ float p = left / lifespan;
+ am = p > 0.8f ? (1 - p) * 5 : 1;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/EnergyParticle.java b/src/com/watabou/pixeldungeon/effects/particles/EnergyParticle.java
new file mode 100644
index 0000000000..31c97d5c61
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/EnergyParticle.java
@@ -0,0 +1,65 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class EnergyParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((EnergyParticle)emitter.recycle( EnergyParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public EnergyParticle() {
+ super();
+
+ lifespan = 1f;
+ color( 0xFFFFAA );
+
+ speed.polar( Random.Float( 2 * PointF.PI ), Random.Float( 24, 32 ) );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ left = lifespan;
+
+ this.x = x - speed.x * lifespan;
+ this.y = y - speed.y * lifespan;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ am = p < 0.5f ? p * p * 4 : (1 - p) * 2;
+ size( Random.Float( 5 * left / lifespan ) );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/FlameParticle.java b/src/com/watabou/pixeldungeon/effects/particles/FlameParticle.java
new file mode 100644
index 0000000000..ae344f5d46
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/FlameParticle.java
@@ -0,0 +1,64 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+
+public class FlameParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((FlameParticle)emitter.recycle( FlameParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public FlameParticle() {
+ super();
+
+ color( 0xEE7722 );
+ lifespan = 0.6f;
+
+ acc.set( 0, -80 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+
+ size = 4;
+ speed.set( 0 );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ float p = left / lifespan;
+ am = p > 0.8f ? (1 - p) * 5 : 1;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/FlowParticle.java b/src/com/watabou/pixeldungeon/effects/particles/FlowParticle.java
new file mode 100644
index 0000000000..e62780075e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/FlowParticle.java
@@ -0,0 +1,109 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Group;
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class FlowParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((FlowParticle)emitter.recycle( FlowParticle.class )).reset( x, y );
+ }
+ };
+
+ public FlowParticle() {
+ super();
+
+ lifespan = 0.6f;
+ acc.set( 0, 32 );
+ angularSpeed = Random.Float( -360, +360 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ left = lifespan;
+
+ this.x = x;
+ this.y = y;
+
+ am = 0;
+ size( 0 );
+ speed.set( 0 );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ am = (p < 0.5f ? p : 1 - p) * 0.6f;
+ size( (1 - p) * 4 );
+ }
+
+ public static class Flow extends Group {
+
+ private static final float DELAY = 0.1f;
+
+ private int pos;
+
+ private float x;
+ private float y;
+
+ private float delay;
+
+ public Flow( int pos ) {
+ super();
+
+ this.pos = pos;
+
+ PointF p = DungeonTilemap.tileToWorld( pos );
+ x = p.x;
+ y = p.y + DungeonTilemap.SIZE - 1;
+
+ delay = Random.Float( DELAY );
+ }
+
+ @Override
+ public void update() {
+
+ if (visible = Dungeon.visible[pos]) {
+
+ super.update();
+
+ if ((delay -= Game.elapsed) <= 0) {
+
+ delay = Random.Float( DELAY );
+
+ ((FlowParticle)recycle( FlowParticle.class )).reset(
+ x + Random.Float( DungeonTilemap.SIZE ), y );
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/LeafParticle.java b/src/com/watabou/pixeldungeon/effects/particles/LeafParticle.java
new file mode 100644
index 0000000000..a0112a417b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/LeafParticle.java
@@ -0,0 +1,69 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.Random;
+
+public class LeafParticle extends PixelParticle.Shrinking {
+
+ public static int color1;
+ public static int color2;
+
+
+ public static final Emitter.Factory GENERAL = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ LeafParticle p = ((LeafParticle)emitter.recycle( LeafParticle.class ));
+ p.color( ColorMath.random( 0x004400, 0x88CC44 ) );
+ p.reset( x, y );
+ }
+ };
+
+ public static final Emitter.Factory LEVEL_SPECIFIC = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ LeafParticle p = ((LeafParticle)emitter.recycle( LeafParticle.class ));
+ p.color( ColorMath.random( Dungeon.level.color1, Dungeon.level.color2 ) );
+ p.reset( x, y );
+ }
+ };
+
+ public LeafParticle() {
+ super();
+
+ lifespan = 1.2f;
+ acc.set( 0, 25 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ speed.set( Random.Float( -8, +8 ), -20 );
+
+ left = lifespan;
+ size = Random.Float( 2, 3 );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/PoisonParticle.java b/src/com/watabou/pixeldungeon/effects/particles/PoisonParticle.java
new file mode 100644
index 0000000000..16554f67a0
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/PoisonParticle.java
@@ -0,0 +1,88 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.Random;
+
+public class PoisonParticle extends PixelParticle {
+
+ public static final Emitter.Factory MISSILE = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((PoisonParticle)emitter.recycle( PoisonParticle.class )).resetMissile( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public static final Emitter.Factory SPLASH = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((PoisonParticle)emitter.recycle( PoisonParticle.class )).resetSplash( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public PoisonParticle() {
+ super();
+
+ lifespan = 0.6f;
+
+ acc.set( 0, +30 );
+ }
+
+ public void resetMissile( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+
+ speed.polar( Random.Float( 3.1415926f ), Random.Float( 6 ) );
+ }
+
+ public void resetSplash( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+
+ speed.polar( Random.Float( 3.1415926f ), Random.Float( 10, 20 ) );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ // alpha: 1 -> 0; size: 1 -> 4
+ size( 4 - (am = left / lifespan) * 3 );
+ // color: 0x8844FF -> 0x00FF00
+ color( ColorMath.interpolate( 0x00FF00, 0x8844FF, am ) );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/PurpleParticle.java b/src/com/watabou/pixeldungeon/effects/particles/PurpleParticle.java
new file mode 100644
index 0000000000..ca378fe525
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/PurpleParticle.java
@@ -0,0 +1,82 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.Random;
+
+public class PurpleParticle extends PixelParticle {
+
+ public static final Emitter.Factory MISSILE = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((PurpleParticle)emitter.recycle( PurpleParticle.class )).reset( x, y );
+ }
+ };
+
+ public static final Emitter.Factory BURST = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((PurpleParticle)emitter.recycle( PurpleParticle.class )).resetBurst( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ }
+ };
+
+ public PurpleParticle() {
+ super();
+
+ lifespan = 0.5f;
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ speed.set( Random.Float( -5, +5 ), Random.Float( -5, +5 ) );
+
+ left = lifespan;
+ }
+
+ public void resetBurst( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ speed.polar( Random.Float( 360 ), Random.Float( 16, 32 ) );
+
+ left = lifespan;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ // alpha: 1 -> 0; size: 1 -> 5
+ size( 5 - (am = left / lifespan) * 4 );
+ // color: 0xFF0044 -> 0x220066
+ color( ColorMath.interpolate( 0x220066, 0xFF0044, am ) );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/ShadowParticle.java b/src/com/watabou/pixeldungeon/effects/particles/ShadowParticle.java
new file mode 100644
index 0000000000..0df72649a2
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/ShadowParticle.java
@@ -0,0 +1,93 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class ShadowParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory MISSILE = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ShadowParticle)emitter.recycle( ShadowParticle.class )).reset( x, y );
+ }
+ };
+
+ public static final Emitter.Factory CURSE = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ShadowParticle)emitter.recycle( ShadowParticle.class )).resetCurse( x, y );
+ }
+ };
+
+ public static final Emitter.Factory UP = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ShadowParticle)emitter.recycle( ShadowParticle.class )).resetUp( x, y );
+ }
+ };
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ speed.set( Random.Float( -5, +5 ), Random.Float( -5, +5 ) );
+
+ size = 6;
+ left = lifespan = 0.5f;
+ }
+
+ public void resetCurse( float x, float y ) {
+ revive();
+
+ size = 8;
+ left = lifespan = 0.5f;
+
+ speed.polar( Random.Float( 2 * PointF.PI ), Random.Float( 16, 32 ) );
+ this.x = x - speed.x * lifespan;
+ this.y = y - speed.y * lifespan;
+ }
+
+ public void resetUp( float x, float y ) {
+ revive();
+
+ speed.set( Random.Float( -8, +8 ), Random.Float( -32, -48 ) );
+ this.x = x;
+ this.y = y;
+
+ size = 6;
+ left = lifespan = 1f;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ // alpha: 0 -> 1 -> 0; size: 6 -> 0; color: 0x660044 -> 0x000000
+ color( ColorMath.interpolate( 0x000000, 0x440044, p ) );
+ am = p < 0.5f ? p * p * 4 : (1 - p) * 2;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/ShaftParticle.java b/src/com/watabou/pixeldungeon/effects/particles/ShaftParticle.java
new file mode 100644
index 0000000000..ba4c2769d5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/ShaftParticle.java
@@ -0,0 +1,66 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.Random;
+
+public class ShaftParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((ShaftParticle)emitter.recycle( ShaftParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ }
+ };
+
+ public ShaftParticle() {
+ super();
+
+ lifespan = 1.2f;
+ speed.set( 0, -6 );
+ }
+
+ private float offs;
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ offs = -Random.Float( lifespan );
+ left = lifespan - offs;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ am = p < 0.5f ? p : 1 - p;
+ scale.x = (1 - p) * 4;
+ scale.y = 16 + (1 - p) * 16;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/SnowParticle.java b/src/com/watabou/pixeldungeon/effects/particles/SnowParticle.java
new file mode 100644
index 0000000000..8b4b809ca7
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/SnowParticle.java
@@ -0,0 +1,55 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.Random;
+
+public class SnowParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((SnowParticle)emitter.recycle( SnowParticle.class )).reset( x, y );
+ }
+ };
+
+ public SnowParticle() {
+ super();
+ speed.set( 0, Random.Float( 5, 8 ) );
+ lifespan = 1.2f;
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y - speed.y * lifespan;
+
+ left = lifespan;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ float p = left / lifespan;
+ am = (p < 0.5f ? p : 1 - p) * 1.5f;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/SparkParticle.java b/src/com/watabou/pixeldungeon/effects/particles/SparkParticle.java
new file mode 100644
index 0000000000..f817e432bb
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/SparkParticle.java
@@ -0,0 +1,62 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.Random;
+
+public class SparkParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((SparkParticle)emitter.recycle( SparkParticle.class )).reset( x, y );
+ }
+ @Override
+ public boolean lightMode() {
+ return true;
+ };
+ };
+
+ public SparkParticle() {
+ super();
+
+ size( 2 );
+
+ acc.set( 0, +50 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan = Random.Float( 0.5f, 1.0f );
+
+ speed.polar( Random.Float( 3.1415926f ), Random.Float( 20, 40 ) );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+ size( Random.Float( 5 * left / lifespan ) );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/WebParticle.java b/src/com/watabou/pixeldungeon/effects/particles/WebParticle.java
new file mode 100644
index 0000000000..4b9ef61bb5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/WebParticle.java
@@ -0,0 +1,61 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.Random;
+
+public class WebParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ for (int i=0; i < 3; i++) {
+ ((WebParticle)emitter.recycle( WebParticle.class )).reset( x, y );
+ }
+ }
+ };
+
+ public WebParticle() {
+ super();
+
+ color( 0xCCCCCC );
+ lifespan = 2f;
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan;
+ angle = Random.Float( 360 );
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ am = p < 0.5f ? p : 1 - p;
+ scale.y = 16 + p * 8;
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/WindParticle.java b/src/com/watabou/pixeldungeon/effects/particles/WindParticle.java
new file mode 100644
index 0000000000..676fd29e53
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/WindParticle.java
@@ -0,0 +1,114 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.Group;
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.DungeonTilemap;
+import com.watabou.utils.PointF;
+import com.watabou.utils.Random;
+
+public class WindParticle extends PixelParticle {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((WindParticle)emitter.recycle( WindParticle.class )).reset( x, y );
+ }
+ };
+
+ private static float angle = Random.Float( PointF.PI * 2 );
+ private static PointF speed = new PointF().polar( angle, 5 );
+
+ private float size;
+
+ public WindParticle() {
+ super();
+
+ lifespan = Random.Float( 1, 2 );
+ scale.set( size = Random.Float( 3 ) );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ left = lifespan;
+
+ super.speed.set( WindParticle.speed );
+ super.speed.scale( size );
+
+ this.x = x - super.speed.x * lifespan / 2;
+ this.y = y - super.speed.y * lifespan / 2;
+
+ angle += Random.Float( -0.1f, +0.1f );
+ speed = new PointF().polar( angle, 5 );
+
+ am = 0;
+ }
+
+ @Override
+ public void update() {
+ super.update();
+
+ float p = left / lifespan;
+ am = (p < 0.5f ? p : 1 - p) * size * 0.2f;
+ }
+
+ public static class Wind extends Group {
+
+ private int pos;
+
+ private float x;
+ private float y;
+
+ private float delay;
+
+ public Wind( int pos ) {
+ super();
+
+ this.pos = pos;
+ PointF p = DungeonTilemap.tileToWorld( pos );
+ x = p.x;
+ y = p.y;
+
+ delay = Random.Float( 5 );
+ }
+
+ @Override
+ public void update() {
+
+ if (visible = Dungeon.visible[pos]) {
+
+ super.update();
+
+ if ((delay -= Game.elapsed) <= 0) {
+
+ delay = Random.Float( 5 );
+
+ ((WindParticle)recycle( WindParticle.class )).reset(
+ x + Random.Float( DungeonTilemap.SIZE ),
+ y + Random.Float( DungeonTilemap.SIZE ) );
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/effects/particles/WoolParticle.java b/src/com/watabou/pixeldungeon/effects/particles/WoolParticle.java
new file mode 100644
index 0000000000..1361d962aa
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/effects/particles/WoolParticle.java
@@ -0,0 +1,54 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.effects.particles;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.noosa.particles.PixelParticle;
+import com.watabou.noosa.particles.Emitter.Factory;
+import com.watabou.utils.ColorMath;
+import com.watabou.utils.Random;
+
+public class WoolParticle extends PixelParticle.Shrinking {
+
+ public static final Emitter.Factory FACTORY = new Factory() {
+ @Override
+ public void emit( Emitter emitter, int index, float x, float y ) {
+ ((WoolParticle)emitter.recycle( WoolParticle.class )).reset( x, y );
+ }
+ };
+
+ public WoolParticle() {
+ super();
+
+ color( ColorMath.random( 0x999999, 0xEEEEE0 ) );
+
+ acc.set( 0, -40 );
+ }
+
+ public void reset( float x, float y ) {
+ revive();
+
+ this.x = x;
+ this.y = y;
+
+ left = lifespan = Random.Float( 0.6f, 1f );
+ size = 5;
+
+ speed.set( Random.Float( -10, +10 ), Random.Float( -10, +10 ) );
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/items/Amulet.java b/src/com/watabou/pixeldungeon/items/Amulet.java
new file mode 100644
index 0000000000..09be4e437a
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Amulet.java
@@ -0,0 +1,106 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import com.watabou.noosa.Game;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.Statistics;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.scenes.AmuletScene;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+public class Amulet extends Item {
+
+ private static final String AC_END = "END THE GAME";
+
+ {
+ name = "Amulet of Yendor";
+ image = ItemSpriteSheet.AMULET;
+
+ unique = true;
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( AC_END );
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+ if (action == AC_END) {
+
+ showAmuletScene( false );
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ @Override
+ public boolean doPickUp( Hero hero ) {
+ if (super.doPickUp( hero )) {
+
+ if (!Statistics.amuletObtained) {
+ Statistics.amuletObtained = true;
+ Dungeon.win( ResultDescriptions.WIN );
+ Badges.validateVictory();
+
+ showAmuletScene( true );
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void showAmuletScene( boolean showText ) {
+ try {
+ Dungeon.saveAll();
+ AmuletScene.noText = !showText;
+ Game.switchScene( AmuletScene.class );
+ } catch (IOException e) {
+ }
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public String info() {
+ return
+ "The Amulet of Yendor is the most powerful known artifact of unknown origin. It is said that the amulet " +
+ "is able to fulfil any wish if its owner's will-power is strong enough to \"persuade\" it to do it.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/Ankh.java b/src/com/watabou/pixeldungeon/items/Ankh.java
new file mode 100644
index 0000000000..d7e94f08a1
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Ankh.java
@@ -0,0 +1,51 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+public class Ankh extends Item {
+
+ {
+ stackable = true;
+ name = "Ankh";
+ image = ItemSpriteSheet.ANKH;
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ @Override
+ public String info() {
+ return
+ "The ancient symbol of immortality grants an ability to return to life after death. " +
+ "Upon resurrection all non-equipped items are lost.";
+ }
+
+ @Override
+ public int price() {
+ return 50 * quantity;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/ArmorKit.java b/src/com/watabou/pixeldungeon/items/ArmorKit.java
new file mode 100644
index 0000000000..487de42bc1
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/ArmorKit.java
@@ -0,0 +1,124 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.ClassArmor;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.HeroSprite;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.windows.WndBag;
+
+public class ArmorKit extends Item {
+
+ private static final String TXT_SELECT_ARMOR = "Select an armor to upgrade";
+ private static final String TXT_UPGRADED = "you applied the armor kit to upgrade your %s";
+
+ private static final float TIME_TO_UPGRADE = 2;
+
+ private static final String AC_APPLY = "APPLY";
+
+ {
+ name = "armor kit";
+ image = ItemSpriteSheet.KIT;
+
+ unique = true;
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( AC_APPLY );
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+ if (action == AC_APPLY) {
+
+ curUser = hero;
+ GameScene.selectItem( itemSelector, WndBag.Mode.ARMOR, TXT_SELECT_ARMOR );
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ private void upgrade( Armor armor ) {
+
+ detach( curUser.belongings.backpack );
+
+ curUser.sprite.centerEmitter().start( Speck.factory( Speck.KIT ), 0.05f, 10 );
+ curUser.spend( TIME_TO_UPGRADE );
+ curUser.busy();
+
+ GLog.w( TXT_UPGRADED, armor.name() );
+
+ ClassArmor classArmor = ClassArmor.upgrade( curUser, armor );
+ if (curUser.belongings.armor == armor) {
+
+ curUser.belongings.armor = classArmor;
+ ((HeroSprite)curUser.sprite).updateArmor();
+
+ } else {
+
+ armor.detach( curUser.belongings.backpack );
+ classArmor.collect( curUser.belongings.backpack );
+
+ }
+
+ curUser.sprite.operate( curUser.pos );
+ Sample.INSTANCE.play( Assets.SND_EVOKE );
+ }
+
+ @Override
+ public String info() {
+ return
+ "Using this kit of small tools and materials anybody can transform any armor into an \"epic armor\", " +
+ "which will keep all properties of the original armor, but will also provide its wearer a special ability " +
+ "depending on his class. No skills in tailoring, leatherworking or blacksmithing are required.";
+ }
+
+ private final WndBag.Listener itemSelector = new WndBag.Listener() {
+ @Override
+ public void onSelect( Item item ) {
+ if (item != null) {
+ ArmorKit.this.upgrade( (Armor)item );
+ }
+ }
+ };
+}
diff --git a/src/com/watabou/pixeldungeon/items/DewVial.java b/src/com/watabou/pixeldungeon/items/DewVial.java
new file mode 100644
index 0000000000..57763345c8
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/DewVial.java
@@ -0,0 +1,187 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.effects.particles.ShaftParticle;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+
+public class DewVial extends Item {
+
+ private static final int MAX_VOLUME = 10;
+
+ private static final String AC_DRINK = "DRINK";
+
+ private static final float TIME_TO_DRINK = 1f;
+
+ private static final String TXT_VALUE = "%+dHP";
+ private static final String TXT_STATUS = "%d/%d";
+
+ private static final String TXT_AUTO_DRINK = "The dew vial was emptied to heal your wounds.";
+ private static final String TXT_COLLECTED = "You collected a dewdrop into your dew vial.";
+ private static final String TXT_FULL = "Your dew vial is full!";
+ private static final String TXT_EMPTY = "Your dew vial is empty!";
+
+ {
+ name = "dew vial";
+ image = ItemSpriteSheet.VIAL;
+
+ defaultAction = AC_DRINK;
+
+ unique = true;
+ }
+
+ private int volume = 0;
+
+ private static final String VOLUME = "volume";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( VOLUME, volume );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ volume = bundle.getInt( VOLUME );
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ if (volume > 0) {
+ actions.add( AC_DRINK );
+ }
+ return actions;
+ }
+
+ private static final double NUM = 20;
+ private static final double POW = Math.log10( NUM );
+
+ @Override
+ public void execute( final Hero hero, String action ) {
+ if (action.equals( AC_DRINK )) {
+
+ if (volume > 0) {
+
+ int value = (int)Math.ceil( Math.pow( volume, POW ) / NUM * hero.HT );
+ int effect = Math.min( hero.HT - hero.HP, value );
+ if (effect > 0) {
+ hero.HP += effect;
+ hero.sprite.emitter().burst( Speck.factory( Speck.HEALING ), volume > 5 ? 2 : 1 );
+ hero.sprite.showStatus( CharSprite.POSITIVE, TXT_VALUE, effect );
+ }
+
+ volume = 0;
+
+ hero.spend( TIME_TO_DRINK );
+ hero.busy();
+
+ Sample.INSTANCE.play( Assets.SND_DRINK );
+ hero.sprite.operate( hero.pos );
+
+ updateQuickslot();
+
+ } else {
+ GLog.w( TXT_EMPTY );
+ }
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ public boolean isFull() {
+ return volume >= MAX_VOLUME;
+ }
+
+ public void collectDew( Dewdrop dew ) {
+
+ GLog.i( TXT_COLLECTED );
+ volume += dew.quantity;
+ if (volume >= MAX_VOLUME) {
+ volume = MAX_VOLUME;
+ GLog.p( TXT_FULL );
+ }
+
+ updateQuickslot();
+ }
+
+ public void fill() {
+ volume = MAX_VOLUME;
+ updateQuickslot();
+ }
+
+ public static void autoDrink( Hero hero ) {
+ DewVial vial = hero.belongings.getItem( DewVial.class );
+ if (vial != null && vial.isFull()) {
+ vial.execute( hero );
+ hero.sprite.emitter().start( ShaftParticle.FACTORY, 0.2f, 3 );
+
+ GLog.w( TXT_AUTO_DRINK );
+ }
+ }
+
+ private static final Glowing WHITE = new Glowing( 0xFFFFCC );
+
+ @Override
+ public Glowing glowing() {
+ return isFull() ? WHITE : null;
+ }
+
+ @Override
+ public String status() {
+ return Utils.format( TXT_STATUS, volume, MAX_VOLUME );
+ }
+
+ @Override
+ public String info() {
+ return
+ "You can store excess dew in this tiny vessel for drinking it later. " +
+ "If the vial is full, in a moment of deadly peril the dew will be " +
+ "consumed automatically.";
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " (" + status() + ")" ;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/Dewdrop.java b/src/com/watabou/pixeldungeon/items/Dewdrop.java
new file mode 100644
index 0000000000..6bcc6f4e45
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Dewdrop.java
@@ -0,0 +1,75 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+public class Dewdrop extends Item {
+
+ private static final String TXT_VALUE = "%+dHP";
+
+ {
+ name = "dewdrop";
+ image = ItemSpriteSheet.DEWDROP;
+
+ stackable = true;
+ }
+
+ @Override
+ public boolean doPickUp( Hero hero ) {
+
+ DewVial vial = hero.belongings.getItem( DewVial.class );
+
+ if (hero.HP < hero.HT || vial == null || vial.isFull()) {
+
+ int value = 1 + (Dungeon.depth - 1) / 5;
+ if (hero.heroClass == HeroClass.HUNTRESS) {
+ value++;
+ }
+
+ int effect = Math.min( hero.HT - hero.HP, value * quantity );
+ if (effect > 0) {
+ hero.HP += effect;
+ hero.sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ hero.sprite.showStatus( CharSprite.POSITIVE, TXT_VALUE, effect );
+ }
+
+ } else if (vial != null) {
+
+ vial.collectDew( this );
+
+ }
+
+ Sample.INSTANCE.play( Assets.SND_DEWDROP );
+ hero.spendAndNext( TIME_TO_PICK_UP );
+
+ return true;
+ }
+
+ @Override
+ public String info() {
+ return "A crystal clear dewdrop.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/EquipableItem.java b/src/com/watabou/pixeldungeon/items/EquipableItem.java
new file mode 100644
index 0000000000..baa71ed1d5
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/EquipableItem.java
@@ -0,0 +1,68 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.particles.ShadowParticle;
+
+public abstract class EquipableItem extends Item {
+
+ public static final String AC_EQUIP = "EQUIP";
+ public static final String AC_UNEQUIP = "UNEQUIP";
+
+ @Override
+ public void execute( Hero hero, String action ) {
+ if (action.equals( AC_EQUIP )) {
+ doEquip( hero );
+ } else if (action.equals( AC_UNEQUIP )) {
+ doUnequip( hero, true );
+ } else {
+ super.execute( hero, action );
+ }
+ }
+
+ @Override
+ public void doDrop( Hero hero ) {
+ if (!isEquipped( hero ) || doUnequip( hero, false )) {
+ super.doDrop( hero );
+ }
+ }
+
+ @Override
+ public void cast( final Hero user, int dst ) {
+
+ if (isEquipped( user )) {
+
+ if (quantity == 1 && !this.doUnequip( user, false )) {
+ return;
+ }
+ }
+
+ super.cast( user, dst );
+ }
+
+ protected static void equipCursed( Hero hero ) {
+ hero.sprite.emitter().burst( ShadowParticle.CURSE, 6 );
+ Sample.INSTANCE.play( Assets.SND_CURSED );
+ }
+
+ public abstract boolean doEquip( Hero hero );
+ public abstract boolean doUnequip( Hero hero, boolean collect );
+}
diff --git a/src/com/watabou/pixeldungeon/items/Generator.java b/src/com/watabou/pixeldungeon/items/Generator.java
new file mode 100644
index 0000000000..31df0d6712
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Generator.java
@@ -0,0 +1,263 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.HashMap;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.npcs.Wandmaker.Rotberry;
+import com.watabou.pixeldungeon.items.armor.*;
+import com.watabou.pixeldungeon.items.bags.Bag;
+import com.watabou.pixeldungeon.items.food.Food;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.items.food.Pasty;
+import com.watabou.pixeldungeon.items.potions.*;
+import com.watabou.pixeldungeon.items.rings.*;
+import com.watabou.pixeldungeon.items.scrolls.*;
+import com.watabou.pixeldungeon.items.wands.*;
+import com.watabou.pixeldungeon.items.weapon.*;
+import com.watabou.pixeldungeon.items.weapon.melee.*;
+import com.watabou.pixeldungeon.items.weapon.missiles.*;
+import com.watabou.pixeldungeon.plants.*;
+import com.watabou.utils.Random;
+
+public class Generator {
+
+ public static enum Category {
+ WEAPON ( 15, Weapon.class ),
+ ARMOR ( 10, Armor.class ),
+ POTION ( 50, Potion.class ),
+ SCROLL ( 40, Scroll.class ),
+ WAND ( 4, Wand.class ),
+ RING ( 2, Ring.class ),
+ SEED ( 5, Plant.Seed.class ),
+ FOOD ( 0, Food.class ),
+ GOLD ( 50, Gold.class );
+
+ public Class>[] classes;
+ public float[] probs;
+
+ public float prob;
+ public Class extends Item> superClass;
+
+ private Category( float prob, Class extends Item> superClass ) {
+ this.prob = prob;
+ this.superClass = superClass;
+ }
+
+ public static int order( Item item ) {
+ for (int i=0; i < values().length; i++) {
+ if (values()[i].superClass.isInstance( item )) {
+ return i;
+ }
+ }
+
+ return item instanceof Bag ? Integer.MAX_VALUE : Integer.MAX_VALUE - 1;
+ }
+ };
+
+ private static HashMap categoryProbs = new HashMap();
+
+ static {
+
+ Category.GOLD.classes = new Class>[]{
+ Gold.class };
+ Category.GOLD.probs = new float[]{ 1 };
+
+ Category.SCROLL.classes = new Class>[]{
+ ScrollOfIdentify.class,
+ ScrollOfTeleportation.class,
+ ScrollOfRemoveCurse.class,
+ ScrollOfUpgrade.class,
+ ScrollOfRecharging.class,
+ ScrollOfMagicMapping.class,
+ ScrollOfChallenge.class,
+ ScrollOfTerror.class,
+ ScrollOfLullaby.class,
+ ScrollOfWeaponUpgrade.class,
+ ScrollOfPsionicBlast.class,
+ ScrollOfMirrorImage.class };
+ Category.SCROLL.probs = new float[]{ 30, 10, 15, 0, 10, 15, 12, 8, 8, 0, 4, 6 };
+
+ Category.POTION.classes = new Class>[]{
+ PotionOfHealing.class,
+ PotionOfExperience.class,
+ PotionOfToxicGas.class,
+ PotionOfParalyticGas.class,
+ PotionOfLiquidFlame.class,
+ PotionOfLevitation.class,
+ PotionOfStrength.class,
+ PotionOfMindVision.class,
+ PotionOfPurity.class,
+ PotionOfInvisibility.class,
+ PotionOfMight.class,
+ PotionOfFrost.class };
+ Category.POTION.probs = new float[]{ 45, 4, 15, 10, 15, 10, 0, 20, 12, 10, 0, 10 };
+
+ Category.WAND.classes = new Class>[]{
+ WandOfTeleportation.class,
+ WandOfSlowness.class,
+ WandOfFirebolt.class,
+ WandOfRegrowth.class,
+ WandOfPoison.class,
+ WandOfBlink.class,
+ WandOfLightning.class,
+ WandOfAmok.class,
+ WandOfTelekinesis.class,
+ WandOfFlock.class,
+ WandOfMagicMissile.class,
+ WandOfDisintegration.class,
+ WandOfAvalanche.class };
+ Category.WAND.probs = new float[]{ 10, 10, 15, 6, 10, 11, 15, 10, 6, 10, 0, 5, 5 };
+
+ Category.WEAPON.classes = new Class>[]{
+ Dagger.class,
+ Knuckles.class,
+ Quarterstaff.class,
+ Spear.class,
+ Mace.class,
+ Sword.class,
+ Longsword.class,
+ BattleAxe.class,
+ WarHammer.class,
+ Glaive.class,
+ ShortSword.class,
+ Dart.class,
+ Javelin.class,
+ IncendiaryDart.class,
+ CurareDart.class,
+ Shuriken.class,
+ Boomerang.class,
+ Tamahawk.class };
+ Category.WEAPON.probs = new float[]{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1 };
+
+ Category.ARMOR.classes = new Class>[]{
+ ClothArmor.class,
+ LeatherArmor.class,
+ MailArmor.class,
+ ScaleArmor.class,
+ PlateArmor.class };
+ Category.ARMOR.probs = new float[]{ 1, 1, 1, 1, 1 };
+
+ Category.FOOD.classes = new Class>[]{
+ Food.class,
+ Pasty.class,
+ MysteryMeat.class };
+ Category.FOOD.probs = new float[]{ 4, 1, 0 };
+
+ Category.RING.classes = new Class>[]{
+ RingOfMending.class,
+ RingOfDetection.class,
+ RingOfShadows.class,
+ RingOfPower.class,
+ RingOfHerbalism.class,
+ RingOfAccuracy.class,
+ RingOfEvasion.class,
+ RingOfSatiety.class,
+ RingOfHaste.class,
+ RingOfElements.class,
+ RingOfHaggler.class,
+ RingOfThorns.class };
+ Category.RING.probs = new float[]{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 };
+
+ Category.SEED.classes = new Class>[]{
+ Firebloom.Seed.class,
+ Icecap.Seed.class,
+ Sorrowmoss.Seed.class,
+ Blindweed.Seed.class,
+ Sungrass.Seed.class,
+ Earthroot.Seed.class,
+ Fadeleaf.Seed.class,
+ Rotberry.Seed.class };
+ Category.SEED.probs = new float[]{ 1, 1, 1, 1, 1, 1, 1, 0 };
+ }
+
+ public static void reset() {
+ for (Category cat : Category.values()) {
+ categoryProbs.put( cat, cat.prob );
+ }
+ }
+
+ public static Item random() {
+ return random( Random.chances( categoryProbs ) );
+ }
+
+ public static Item random( Category cat ) {
+ try {
+
+ categoryProbs.put( cat, categoryProbs.get( cat ) / 2 );
+
+ switch (cat) {
+ case ARMOR:
+ return randomArmor();
+ case WEAPON:
+ return randomWeapon();
+ default:
+ return ((Item)cat.classes[Random.chances( cat.probs )].newInstance()).random();
+ }
+
+ } catch (Exception e) {
+
+ return null;
+
+ }
+ }
+
+ public static Item random( Class extends Item> cl ) {
+ try {
+
+ return ((Item)cl.newInstance()).random();
+
+ } catch (Exception e) {
+
+ return null;
+
+ }
+ }
+
+ public static Armor randomArmor() throws Exception {
+
+ int curStr = Hero.STARTING_STR + Dungeon.potionOfStrength;
+
+ Category cat = Category.ARMOR;
+
+ Armor a1 = (Armor)cat.classes[Random.chances( cat.probs )].newInstance();
+ Armor a2 = (Armor)cat.classes[Random.chances( cat.probs )].newInstance();
+
+ a1.random();
+ a2.random();
+
+ return Math.abs( curStr - a1.STR ) < Math.abs( curStr - a2.STR ) ? a1 : a2;
+ }
+
+ public static Weapon randomWeapon() throws Exception {
+
+ int curStr = Hero.STARTING_STR + Dungeon.potionOfStrength;
+
+ Category cat = Category.WEAPON;
+
+ Weapon w1 = (Weapon)cat.classes[Random.chances( cat.probs )].newInstance();
+ Weapon w2 = (Weapon)cat.classes[Random.chances( cat.probs )].newInstance();
+
+ w1.random();
+ w2.random();
+
+ return Math.abs( curStr - w1.STR ) < Math.abs( curStr - w2.STR ) ? w1 : w2;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/Gold.java b/src/com/watabou/pixeldungeon/items/Gold.java
new file mode 100644
index 0000000000..26ea0f86bc
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Gold.java
@@ -0,0 +1,118 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Statistics;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Gold extends Item {
+
+ private static final String TXT_COLLECT = "Collect gold coins to spend them later in a shop.";
+ private static final String TXT_INFO = "A pile of %d gold coins. " + TXT_COLLECT;
+ private static final String TXT_INFO_1 = "One gold coin. " + TXT_COLLECT;
+ private static final String TXT_VALUE = "%+d";
+
+ {
+ name = "gold";
+ image = ItemSpriteSheet.GOLD;
+ stackable = true;
+ }
+
+ public Gold() {
+ this( 1 );
+ }
+
+ public Gold( int value ) {
+ this.quantity = value;
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ return new ArrayList();
+ }
+
+ @Override
+ public boolean doPickUp( Hero hero ) {
+
+ Dungeon.gold += quantity;
+ Statistics.goldCollected += quantity;
+ Badges.validateGoldCollected();
+
+ GameScene.pickUp( this );
+ hero.sprite.showStatus( CharSprite.NEUTRAL, TXT_VALUE, quantity );
+ hero.spendAndNext( TIME_TO_PICK_UP );
+
+ Sample.INSTANCE.play( Assets.SND_GOLD, 1, 1, Random.Float( 0.9f, 1.1f ) );
+
+ return true;
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ @Override
+ public String info() {
+ switch (quantity) {
+ case 0:
+ return TXT_COLLECT;
+ case 1:
+ return TXT_INFO_1;
+ default:
+ return Utils.format( TXT_INFO, quantity );
+ }
+ }
+
+ @Override
+ public Item random() {
+ quantity = Random.Int( 20 + Dungeon.depth * 10, 40 + Dungeon.depth * 20 );
+ return this;
+ }
+
+ private static final String VALUE = "value";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( VALUE, quantity );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle(bundle);
+ quantity = bundle.getInt( VALUE );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/Heap.java b/src/com/watabou/pixeldungeon/items/Heap.java
new file mode 100644
index 0000000000..59325bcb07
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Heap.java
@@ -0,0 +1,337 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.Statistics;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.Wraith;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.effects.Splash;
+import com.watabou.pixeldungeon.effects.particles.ElmoParticle;
+import com.watabou.pixeldungeon.effects.particles.ShadowParticle;
+import com.watabou.pixeldungeon.items.food.ChargrilledMeat;
+import com.watabou.pixeldungeon.items.food.FrozenCarpaccio;
+import com.watabou.pixeldungeon.items.food.MysteryMeat;
+import com.watabou.pixeldungeon.items.scrolls.Scroll;
+import com.watabou.pixeldungeon.plants.Plant.Seed;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Heap implements Bundlable {
+
+ private static final int SEEDS_TO_POTION = 3;
+
+ public enum Type {
+ HEAP,
+ FOR_SALE,
+ CHEST,
+ LOCKED_CHEST,
+ CRYSTAL_CHEST,
+ TOMB,
+ SKELETON
+ }
+ public Type type = Type.HEAP;
+
+ public int pos = 0;
+
+ public ItemSprite sprite;
+
+ protected LinkedList
- items = new LinkedList
- ();
+
+ public int image() {
+ switch (type) {
+ case HEAP:
+ case FOR_SALE:
+ return size() > 0 ? items.peek().image() : 0;
+ case CHEST:
+ return ItemSpriteSheet.CHEST;
+ case LOCKED_CHEST:
+ return ItemSpriteSheet.LOCKED_CHEST;
+ case CRYSTAL_CHEST:
+ return ItemSpriteSheet.CRYSTAL_CHEST;
+ case TOMB:
+ return ItemSpriteSheet.TOMB;
+ case SKELETON:
+ return ItemSpriteSheet.BONES;
+ default:
+ return 0;
+ }
+ }
+
+ public ItemSprite.Glowing glowing() {
+ return (type == Type.HEAP || type == Type.FOR_SALE) && items.size() > 0 ? items.peek().glowing() : null;
+ }
+
+ public void open( Hero hero ) {
+ switch (type) {
+ case TOMB:
+ Wraith.spawnAround( hero.pos );
+ break;
+ case SKELETON:
+ CellEmitter.center( pos ).start( Speck.factory( Speck.RATTLE ), 0.1f, 3 );
+ for (Item item : items) {
+ if (item.cursed) {
+ if (Wraith.spawnAt( pos ) == null) {
+ hero.sprite.emitter().burst( ShadowParticle.CURSE, 6 );
+ hero.damage( hero.HP / 2, this );
+ }
+ Sample.INSTANCE.play( Assets.SND_CURSED );
+ break;
+ }
+ }
+ break;
+ default:
+ }
+
+ type = Type.HEAP;
+ sprite.link();
+ sprite.drop();
+ }
+
+ public int size() {
+ return items.size();
+ }
+
+ public Item pickUp() {
+
+ Item item = items.removeFirst();
+ if (items.isEmpty()) {
+ destroy();
+ } else if (sprite != null) {
+ sprite.view( image(), glowing() );
+ }
+
+ return item;
+ }
+
+ public Item peek() {
+ return items.peek();
+ }
+
+ public void drop( Item item ) {
+
+ if (item.stackable) {
+
+ for (Item i : items) {
+ if (i.isSimilar( item )) {
+ i.quantity += item.quantity;
+ item = i;
+ break;
+ }
+ }
+ items.remove( item );
+
+ }
+
+ if (item instanceof Dewdrop) {
+ items.add( item );
+ } else {
+ items.addFirst( item );
+ }
+
+ if (sprite != null) {
+ sprite.view( image(), glowing() );
+ }
+ }
+
+ public void replace( Item a, Item b ) {
+ int index = items.indexOf( a );
+ if (index != -1) {
+ items.remove( index );
+ items.add( index, b );
+ }
+ }
+
+ public void burn() {
+
+ if (type != Type.HEAP) {
+ return;
+ }
+
+ boolean burnt = false;
+ boolean evaporated = false;
+
+ for (Item item : items.toArray( new Item[0] )) {
+ if (item instanceof Scroll) {
+ items.remove( item );
+ burnt = true;
+ } else if (item instanceof Dewdrop) {
+ items.remove( item );
+ evaporated = true;
+ } else if (item instanceof MysteryMeat) {
+ replace( item, ChargrilledMeat.cook( (MysteryMeat)item ) );
+ burnt = true;
+ }
+ }
+
+ if (burnt || evaporated) {
+
+ if (Dungeon.visible[pos]) {
+ if (burnt) {
+ burnFX( pos );
+ } else {
+ evaporateFX( pos );
+ }
+ }
+
+ if (isEmpty()) {
+ destroy();
+ } else if (sprite != null) {
+ sprite.view( image(), glowing() );
+ }
+
+ }
+ }
+
+ public void freeze() {
+
+ if (type != Type.HEAP) {
+ return;
+ }
+
+ boolean frozen = false;
+ for (Item item : items.toArray( new Item[0] )) {
+ if (item instanceof MysteryMeat) {
+ replace( item, FrozenCarpaccio.cook( (MysteryMeat)item ) );
+ frozen = true;
+ }
+ }
+
+ if (frozen) {
+ if (isEmpty()) {
+ destroy();
+ } else if (sprite != null) {
+ sprite.view( image(), glowing() );
+ }
+ }
+ }
+
+ public Item transmute() {
+
+ CellEmitter.get( pos ).burst( Speck.factory( Speck.BUBBLE ), 3 );
+ Splash.at( pos, 0xFFFFFF, 3 );
+
+ float chances[] = new float[items.size()];
+ int count = 0;
+
+ int index = 0;
+ for (Item item : items) {
+ if (item instanceof Seed) {
+ count += item.quantity;
+ chances[index++] = item.quantity;
+ } else {
+ count = 0;
+ break;
+ }
+ }
+
+ if (count >= SEEDS_TO_POTION) {
+
+ CellEmitter.get( pos ).burst( Speck.factory( Speck.WOOL ), 6 );
+ Sample.INSTANCE.play( Assets.SND_PUFF );
+
+ if (Random.Int( count ) == 0) {
+
+ CellEmitter.center( pos ).burst( Speck.factory( Speck.EVOKE ), 3 );
+
+ destroy();
+
+ Statistics.potionsCooked++;
+ Badges.validatePotionsCooked();
+
+ return Generator.random( Generator.Category.POTION );
+
+ } else {
+
+ Seed proto = (Seed)items.get( Random.chances( chances ) );
+ Class extends Item> itemClass = proto.alchemyClass;
+
+ destroy();
+
+ Statistics.potionsCooked++;
+ Badges.validatePotionsCooked();
+
+ if (itemClass == null) {
+ return Generator.random( Generator.Category.POTION );
+ } else {
+ try {
+ return itemClass.newInstance();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ }
+
+ } else {
+ return null;
+ }
+ }
+
+ public static void burnFX( int pos ) {
+ CellEmitter.get( pos ).burst( ElmoParticle.FACTORY, 6 );
+ Sample.INSTANCE.play( Assets.SND_BURNING );
+ }
+
+ public static void evaporateFX( int pos ) {
+ CellEmitter.get( pos ).burst( Speck.factory( Speck.STEAM ), 5 );
+ }
+
+ public boolean isEmpty() {
+ return items == null || items.size() == 0;
+ }
+
+ public void destroy() {
+ Dungeon.level.heaps.remove( this.pos );
+ if (sprite != null) {
+ sprite.kill();
+ }
+ items.clear();
+ items = null;
+ }
+
+ private static final String POS = "pos";
+ private static final String TYPE = "type";
+ private static final String ITEMS = "items";
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ pos = bundle.getInt( POS );
+ type = Type.valueOf( bundle.getString( TYPE ) );
+ items = new LinkedList
- ( (Collection extends Item>) bundle.getCollection( ITEMS ) );
+ }
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ bundle.put( POS, pos );
+ bundle.put( TYPE, type.toString() );
+ bundle.put( ITEMS, items );
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/items/Item.java b/src/com/watabou/pixeldungeon/items/Item.java
new file mode 100644
index 0000000000..47d6c9328b
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Item.java
@@ -0,0 +1,463 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.SnipersMark;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.bags.Bag;
+import com.watabou.pixeldungeon.items.weapon.missiles.MissileWeapon;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.scenes.CellSelector;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.MissileSprite;
+import com.watabou.pixeldungeon.ui.QuickSlot;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Callback;
+
+public class Item implements Bundlable {
+
+ private static final String TXT_PACK_FULL = "Your pack is too full for the %s";
+
+ private static final String TXT_TO_STRING = "%s";
+ private static final String TXT_TO_STRING_X = "%s x%d";
+ private static final String TXT_TO_STRING_LVL = "%s%+d";
+ private static final String TXT_TO_STRING_LVL_X = "%s%+d x%d";
+
+ protected static final float TIME_TO_THROW = 1.0f;
+ protected static final float TIME_TO_PICK_UP = 1.0f;
+ protected static final float TIME_TO_DROP = 0.5f;
+
+ public static final String AC_DROP = "DROP";
+ public static final String AC_THROW = "THROW";
+
+ public String defaultAction;
+
+ protected String name = "smth";
+ protected int image = 0;
+
+ public boolean stackable = false;
+ protected int quantity = 1;
+
+ public int level = 0;
+ public boolean levelKnown = false;
+
+ public boolean cursed;
+ public boolean cursedKnown;
+
+ // Unique items persist through revival
+ public boolean unique = false;
+
+ private static Comparator
- itemComparator = new Comparator
- () {
+ @Override
+ public int compare( Item lhs, Item rhs ) {
+ return Generator.Category.order( lhs ) - Generator.Category.order( rhs );
+ }
+ };
+
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = new ArrayList();
+ actions.add( AC_DROP );
+ actions.add( AC_THROW );
+ return actions;
+ }
+
+ public boolean doPickUp( Hero hero ) {
+ if (collect( hero.belongings.backpack )) {
+
+ GameScene.pickUp( this );
+ Sample.INSTANCE.play( Assets.SND_ITEM );
+ hero.spendAndNext( TIME_TO_PICK_UP );
+ return true;
+
+ } else {
+ return false;
+ }
+ }
+
+ public void doDrop( Hero hero ) {
+ hero.spendAndNext( TIME_TO_DROP );
+ Dungeon.level.drop( detachAll( hero.belongings.backpack ), hero.pos ).sprite.drop( hero.pos );
+ }
+
+ public void doThrow( Hero hero ) {
+ GameScene.selectCell( thrower );
+ }
+
+ public void execute( Hero hero, String action ) {
+
+ curUser = hero;
+ curItem = this;
+
+ if (action.equals( AC_DROP )) {
+
+ doDrop( hero );
+
+ } else if (action.equals( AC_THROW )) {
+
+ doThrow( hero );
+
+ }
+ }
+
+ public void execute( Hero hero ) {
+ execute( hero, defaultAction );
+ }
+
+ protected void onThrow( int cell ) {
+ Heap heap = Dungeon.level.drop( this, cell );
+ if (!heap.isEmpty()) {
+ heap.sprite.drop( cell );
+ }
+ }
+
+ public boolean collect( Bag container ) {
+
+ ArrayList
- items = container.items;
+
+ if (items.contains( this )) {
+ return true;
+ }
+
+ for (Item item:items) {
+ if (item instanceof Bag && ((Bag)item).grab( this )) {
+ return collect( (Bag)item );
+ }
+ }
+
+ if (stackable) {
+ for (Item item:items) {
+ if (isSimilar( item )) {
+ item.quantity += quantity;
+ item.updateQuickslot();
+ return true;
+ }
+ }
+ }
+
+ if (items.size() < container.size) {
+
+ if (Dungeon.hero != null && Dungeon.hero.isAlive()) {
+ Badges.validateItemLevelAquired( this );
+ }
+
+ items.add( this );
+ QuickSlot.refresh();
+ Collections.sort( items, itemComparator );
+ return true;
+
+ } else {
+
+ GLog.n( TXT_PACK_FULL, name() );
+ return false;
+
+ }
+ }
+
+ public boolean collect() {
+ return collect( Dungeon.hero.belongings.backpack );
+ }
+
+ public Item detach( Bag container ) {
+
+ if (quantity <= 0) {
+
+ return null;
+
+ } else
+ if (quantity == 1) {
+
+ return detachAll( container );
+
+ } else {
+
+ quantity--;
+ updateQuickslot();
+
+ try {
+ return getClass().newInstance();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ }
+
+ public Item detachAll( Bag container ) {
+ for (Item item : container.items) {
+ if (item == this) {
+ container.items.remove( this );
+ QuickSlot.refresh();
+ return this;
+ } else if (item instanceof Bag) {
+ Bag bag = (Bag)item;
+ if (bag.contains( this )) {
+ detachAll( bag );
+ return this;
+ }
+ }
+ }
+
+ return this;
+ }
+
+ public boolean isSimilar( Item item ) {
+ return getClass() == item.getClass();
+ }
+
+ public Item upgrade() {
+
+ cursed = false;
+ cursedKnown = true;
+ this.level++;
+
+ return this;
+ }
+
+ public Item upgrade( int n ) {
+ for (int i=0; i < n; i++) {
+ upgrade();
+ }
+
+ return this;
+ }
+
+ public Item degrade() {
+
+ this.level--;
+
+ return this;
+ }
+
+ public Item degrade( int n ) {
+ for (int i=0; i < n; i++) {
+ degrade();
+ }
+
+ return this;
+ }
+
+ public int visiblyUpgraded() {
+ return levelKnown ? level : 0;
+ }
+
+ public boolean visiblyCursed() {
+ return cursed && cursedKnown;
+ }
+
+ public boolean isUpgradable() {
+ return true;
+ }
+
+ public boolean isIdentified() {
+ return levelKnown && cursedKnown;
+ }
+
+ public boolean isEquipped( Hero hero ) {
+ return false;
+ }
+
+ public Item identify() {
+
+ levelKnown = true;
+ cursedKnown = true;
+
+ return this;
+ }
+
+ public static void evoke( Hero hero ) {
+ hero.sprite.emitter().burst( Speck.factory( Speck.EVOKE ), 5 );
+ }
+
+ @Override
+ public String toString() {
+
+ if (levelKnown && level != 0) {
+ if (quantity > 1) {
+ return Utils.format( TXT_TO_STRING_LVL_X, name(), level, quantity );
+ } else {
+ return Utils.format( TXT_TO_STRING_LVL, name(), level );
+ }
+ } else {
+ if (quantity > 1) {
+ return Utils.format( TXT_TO_STRING_X, name(), quantity );
+ } else {
+ return Utils.format( TXT_TO_STRING, name() );
+ }
+ }
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public final String trueName() {
+ return name;
+ }
+
+ public int image() {
+ return image;
+ }
+
+ public ItemSprite.Glowing glowing() {
+ return null;
+ }
+
+ public String info() {
+ return desc();
+ }
+
+ public String desc() {
+ return "";
+ }
+
+ public int quantity() {
+ return quantity;
+ }
+
+ public void quantity( int value ) {
+ quantity = value;
+ }
+
+ public int price() {
+ return 0;
+ }
+
+ public static Item virtual( Class extends Item> cl ) {
+ try {
+
+ Item item = (Item)cl.newInstance();
+ item.quantity = 0;
+ return item;
+
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public Item random() {
+ return this;
+ }
+
+ public String status() {
+ return quantity != 1 ? Integer.toString( quantity ) : null;
+ }
+
+ public void updateQuickslot() {
+ if ((stackable && Dungeon.quickslot == getClass()) || Dungeon.quickslot == this) {
+ QuickSlot.refresh();
+ }
+ }
+
+ private static final String QUANTITY = "quantity";
+ private static final String LEVEL = "level";
+ private static final String LEVEL_KNOWN = "levelKnown";
+ private static final String CURSED = "cursed";
+ private static final String CURSED_KNOWN = "cursedKnown";
+ private static final String QUICKSLOT = "quickslot";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ bundle.put( QUANTITY, quantity );
+ bundle.put( LEVEL, level );
+ bundle.put( LEVEL_KNOWN, levelKnown );
+ bundle.put( CURSED, cursed );
+ bundle.put( CURSED_KNOWN, cursedKnown );
+ if (this == Dungeon.quickslot) {
+ bundle.put( QUICKSLOT, true );
+ }
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ quantity = bundle.getInt( QUANTITY );
+ levelKnown = bundle.getBoolean( LEVEL_KNOWN );
+ cursedKnown = bundle.getBoolean( CURSED_KNOWN );
+
+ int level = bundle.getInt( LEVEL );
+ if (level > 0) {
+ upgrade( level );
+ } else if (level < 0) {
+ degrade( -level );
+ }
+
+ cursed = bundle.getBoolean( CURSED );
+
+ if (bundle.getBoolean( QUICKSLOT )) {
+ Dungeon.quickslot = this;
+ }
+ }
+
+ public void cast( final Hero user, int dst ) {
+
+ final int cell = Ballistica.cast( user.pos, dst, false, true );
+ user.sprite.zap( cell );
+ user.busy();
+
+ Char enemy = Actor.findChar( cell );
+ QuickSlot.target( this, enemy );
+
+ float delay = TIME_TO_THROW;
+ if (this instanceof MissileWeapon) {
+
+ // Refactoring needed!
+ delay *= ((MissileWeapon)this).speedFactor( user );
+ if (enemy != null && enemy.buff( SnipersMark.class ) != null) {
+ delay *= 0.5f;
+ }
+ }
+ final float finalDelay = delay;
+
+ ((MissileSprite)user.sprite.parent.recycle( MissileSprite.class )).
+ reset( user.pos, cell, this, new Callback() {
+ @Override
+ public void call() {
+ Item.this.detach( user.belongings.backpack ).onThrow( cell );
+ user.spendAndNext( finalDelay );
+ }
+ } );
+ }
+
+ protected static Hero curUser = null;
+ protected static Item curItem = null;
+ protected static CellSelector.Listener thrower = new CellSelector.Listener() {
+ @Override
+ public void onSelect( Integer target ) {
+ if (target != null) {
+ curItem.cast( curUser, target );
+ }
+ }
+ @Override
+ public String prompt() {
+ return "Choose direction of throw";
+ }
+ };
+}
diff --git a/src/com/watabou/pixeldungeon/items/ItemStatusHandler.java b/src/com/watabou/pixeldungeon/items/ItemStatusHandler.java
new file mode 100644
index 0000000000..ca3e41cd46
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/ItemStatusHandler.java
@@ -0,0 +1,162 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class ItemStatusHandler {
+
+ private Class extends T>[] items;
+
+ private HashMap, Integer> images;
+ private HashMap, String> labels;
+ private HashSet> known;
+
+ public ItemStatusHandler( Class extends T>[] items, String[] allLabels, Integer[] allImages ) {
+
+ this.items = items;
+
+ this.images = new HashMap, Integer>();
+ this.labels = new HashMap, String>();
+ known = new HashSet>();
+
+ ArrayList labelsLeft = new ArrayList( Arrays.asList( allLabels ) );
+ ArrayList imagesLeft = new ArrayList( Arrays.asList( allImages ) );
+
+ for (int i=0; i < items.length; i++) {
+
+ Class extends T> item = (Class extends T>)(items[i]);
+
+ int index = Random.Int( labelsLeft.size() );
+
+ labels.put( item, labelsLeft.get( index ) );
+ labelsLeft.remove( index );
+
+ images.put( item, imagesLeft.get( index ) );
+ imagesLeft.remove( index );
+ }
+ }
+
+ public ItemStatusHandler( Class extends T>[] items, String[] labels, Integer[] images, Bundle bundle ) {
+
+ this.items = items;
+
+ this.images = new HashMap, Integer>();
+ this.labels = new HashMap, String>();
+ known = new HashSet>();
+
+ restore( bundle, labels, images );
+ }
+
+ private static final String PFX_IMAGE = "_image";
+ private static final String PFX_LABEL = "_label";
+ private static final String PFX_KNOWN = "_known";
+
+ public void save( Bundle bundle ) {
+ for (int i=0; i < items.length; i++) {
+ String itemName = items[i].toString();
+ bundle.put( itemName + PFX_IMAGE, images.get( items[i] ) );
+ bundle.put( itemName + PFX_LABEL, labels.get( items[i] ) );
+ bundle.put( itemName + PFX_KNOWN, known.contains( items[i] ) );
+ }
+ }
+
+ private void restore( Bundle bundle, String[] allLabels, Integer[] allImages ) {
+
+ ArrayList labelsLeft = new ArrayList( Arrays.asList( allLabels ) );
+ ArrayList imagesLeft = new ArrayList( Arrays.asList( allImages ) );
+
+ for (int i=0; i < items.length; i++) {
+
+ Class extends T> item = (Class extends T>)(items[i]);
+ String itemName = item.toString();
+
+ if (bundle.contains( itemName + PFX_LABEL )) {
+
+ String label = bundle.getString( itemName + PFX_LABEL );
+ labels.put( item, label );
+ labelsLeft.remove( label );
+
+ Integer image = bundle.getInt( itemName + PFX_IMAGE );
+ images.put( item, image );
+ imagesLeft.remove( image );
+
+ if (bundle.getBoolean( itemName + PFX_KNOWN )) {
+ known.add( item );
+ }
+
+ } else {
+
+ int index = Random.Int( labelsLeft.size() );
+
+ labels.put( item, labelsLeft.get( index ) );
+ labelsLeft.remove( index );
+
+ images.put( item, imagesLeft.get( index ) );
+ imagesLeft.remove( index );
+
+ }
+ }
+ }
+
+ public int image( T item ) {
+ return images.get( item.getClass() );
+ }
+
+ public String label( T item ) {
+ return labels.get( item.getClass() );
+ }
+
+ public boolean isKnown( T item ) {
+ return known.contains( item.getClass() );
+ }
+
+ @SuppressWarnings("unchecked")
+ public void know( T item ) {
+ known.add( (Class extends T>)item.getClass() );
+
+ if (known.size() == items.length - 1) {
+ for (int i=0; i < items.length; i++) {
+ if (!known.contains( items[i] )) {
+ known.add( items[i] );
+ break;
+ }
+ }
+ }
+ }
+
+ public HashSet> known() {
+ return known;
+ }
+
+ public HashSet> unknown() {
+ HashSet> result = new HashSet>();
+ for (Class extends T> i : items) {
+ if (!known.contains( i )) {
+ result.add( i );
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/KindOfWeapon.java b/src/com/watabou/pixeldungeon/items/KindOfWeapon.java
new file mode 100644
index 0000000000..ba651e1890
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/KindOfWeapon.java
@@ -0,0 +1,115 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.ui.QuickSlot;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Random;
+
+public class KindOfWeapon extends EquipableItem {
+
+ private static final String TXT_EQUIP_CURSED = "you wince as your grip involuntarily tightens around your %s";
+ private static final String TXT_UNEQUIP_CURSED = "you can't remove cursed %s!";
+
+ protected static final float TIME_TO_EQUIP = 1f;
+
+ public int MIN = 0;
+ public int MAX = 1;
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( isEquipped( hero ) ? AC_UNEQUIP : AC_EQUIP );
+ return actions;
+ }
+
+ @Override
+ public boolean isEquipped( Hero hero ) {
+ return hero.belongings.weapon == this;
+ }
+
+ @Override
+ public boolean doEquip( Hero hero ) {
+
+ detachAll( hero.belongings.backpack );
+
+ if (hero.belongings.weapon == null || hero.belongings.weapon.doUnequip( hero, true )) {
+
+ hero.belongings.weapon = this;
+ activate( hero );
+
+ QuickSlot.refresh();
+
+ cursedKnown = true;
+ if (cursed) {
+ equipCursed( hero );
+ GLog.n( TXT_EQUIP_CURSED, name() );
+ }
+
+ hero.spendAndNext( TIME_TO_EQUIP );
+ return true;
+
+ } else {
+
+ collect( hero.belongings.backpack );
+ return false;
+ }
+ }
+
+ @Override
+ public boolean doUnequip( Hero hero, boolean collect ) {
+
+ if (cursed) {
+ GLog.w( TXT_UNEQUIP_CURSED, name() );
+ return false;
+ }
+
+ hero.belongings.weapon = null;
+ hero.spendAndNext( TIME_TO_EQUIP );
+
+ if (collect && !collect( hero.belongings.backpack )) {
+ Dungeon.level.drop( this, hero.pos );
+ }
+
+ return true;
+ }
+
+ public void activate( Hero hero ) {
+ }
+
+ public int damageRoll( Hero owner ) {
+ return Random.NormalIntRange( MIN, MAX );
+ }
+
+ public float acuracyFactor( Hero hero ) {
+ return 1f;
+ }
+
+ public float speedFactor( Hero hero ) {
+ return 1f;
+ }
+
+ public void proc( Char attacker, Char defender, int damage ) {
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/items/LloydsBeacon.java b/src/com/watabou/pixeldungeon/items/LloydsBeacon.java
new file mode 100644
index 0000000000..5be193943f
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/LloydsBeacon.java
@@ -0,0 +1,178 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.Game;
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.InterlevelScene;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+
+public class LloydsBeacon extends Item {
+
+ private static final String TXT_PREVENTING =
+ "Strong magic aura of this place prevents you from using the lloyd's beacon!";
+
+ private static final String TXT_CREATURES =
+ "Psychic aura of neighbouring creatures doesn't allow you to use the lloyd's beacon at this moment.";
+
+ private static final String TXT_RETURN =
+ "The lloyd's beacon is successfully set at your current location, now you can return here anytime.";
+
+ private static final String TXT_INFO =
+ "Lloyd's beacon is an intricate magic device, that allows you to return to a place you have already been.";
+
+ private static final String TXT_SET =
+ "\n\nThis beacon was set somewhere on the level %d of Pixel Dungeon.";
+
+ public static final float TIME_TO_USE = 1;
+
+ public static final String AC_SET = "SET";
+ public static final String AC_RETURN = "RETURN";
+
+ private int returnDepth = -1;
+ private int returnPos;
+
+ {
+ name = "lloyd's beacon";
+ image = ItemSpriteSheet.BEACON;
+
+ unique = true;
+ }
+
+ private static final String DEPTH = "depth";
+ private static final String POS = "pos";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( DEPTH, returnDepth );
+ if (returnDepth != -1) {
+ bundle.put( POS, returnPos );
+ }
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle(bundle);
+ returnDepth = bundle.getInt( DEPTH );
+ returnPos = bundle.getInt( POS );
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( AC_SET );
+ if (returnDepth != -1) {
+ actions.add( AC_RETURN );
+ }
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+
+ if (action == AC_SET || action == AC_RETURN) {
+
+ if (Dungeon.bossLevel()) {
+ hero.spend( LloydsBeacon.TIME_TO_USE );
+ GLog.w( TXT_PREVENTING );
+ return;
+ }
+
+ for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
+ if (Actor.findChar( hero.pos + Level.NEIGHBOURS8[i] ) != null) {
+ GLog.w( TXT_CREATURES );
+ return;
+ }
+ }
+ }
+
+ if (action == AC_SET) {
+
+ returnDepth = Dungeon.depth;
+ returnPos = hero.pos;
+
+ hero.spend( LloydsBeacon.TIME_TO_USE );
+ hero.busy();
+
+ hero.sprite.operate( hero.pos );
+ Sample.INSTANCE.play( Assets.SND_BEACON );
+
+ GLog.i( TXT_RETURN );
+
+ } else if (action == AC_RETURN) {
+
+ if (returnDepth == Dungeon.depth) {
+ reset();
+ WandOfBlink.appear( hero, returnPos );
+ Dungeon.level.press( returnPos, hero );
+ Dungeon.observe();
+ } else {
+ InterlevelScene.mode = InterlevelScene.Mode.RETURN;
+ InterlevelScene.returnDepth = returnDepth;
+ InterlevelScene.returnPos = returnPos;
+ reset();
+ Game.switchScene( InterlevelScene.class );
+ }
+
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ public void reset() {
+ returnDepth = -1;
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ private static final Glowing WHITE = new Glowing( 0xFFFFFF );
+
+ @Override
+ public Glowing glowing() {
+ return returnDepth != -1 ? WHITE : null;
+ }
+
+ @Override
+ public String info() {
+ return TXT_INFO + (returnDepth == -1 ? "" : Utils.format( TXT_SET, returnDepth ) );
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/Stylus.java b/src/com/watabou/pixeldungeon/items/Stylus.java
new file mode 100644
index 0000000000..2ad363ff7f
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Stylus.java
@@ -0,0 +1,122 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.particles.PurpleParticle;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.windows.WndBag;
+
+public class Stylus extends Item {
+
+ private static final String TXT_SELECT_ARMOR = "Select an armor to inscribe on";
+ private static final String TXT_INSCRIBED = "you inscribed the %s on your %s";
+
+ private static final float TIME_TO_INSCRIBE = 2;
+
+ private static final String AC_INSCRIBE = "INSCRIBE";
+
+ {
+ name = "arcane stylus";
+ image = ItemSpriteSheet.STYLUS;
+
+ stackable = true;
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( AC_INSCRIBE );
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+ if (action == AC_INSCRIBE) {
+
+ curUser = hero;
+ GameScene.selectItem( itemSelector, WndBag.Mode.ARMOR, TXT_SELECT_ARMOR );
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ private void inscribe( Armor armor ) {
+
+ detach( curUser.belongings.backpack );
+
+ Class extends Armor.Glyph> oldGlyphClass = armor.glyph != null ? armor.glyph.getClass() : null;
+ Armor.Glyph glyph = Armor.Glyph.random();
+ while (glyph.getClass() == oldGlyphClass) {
+ glyph = Armor.Glyph.random();
+ }
+
+ GLog.w( TXT_INSCRIBED, glyph.name(), armor.name() );
+
+ armor.inscribe( glyph );
+
+ curUser.sprite.operate( curUser.pos );
+ curUser.sprite.centerEmitter().start( PurpleParticle.BURST, 0.05f, 10 );
+ Sample.INSTANCE.play( Assets.SND_BURNING );
+
+ curUser.spend( TIME_TO_INSCRIBE );
+ curUser.busy();
+ }
+
+ @Override
+ public int price() {
+ return 50 * quantity;
+ }
+
+ @Override
+ public String info() {
+ return
+ "This arcane stylus is made of some dark, very hard stone. Using it you can inscribe " +
+ "a magical glyph on your armor, but you have no power over choosing what glyph it will be, " +
+ "the stylus will decide it for you.";
+ }
+
+ private final WndBag.Listener itemSelector = new WndBag.Listener() {
+ @Override
+ public void onSelect( Item item ) {
+ if (item != null) {
+ Stylus.this.inscribe( (Armor)item );
+ }
+ }
+ };
+}
diff --git a/src/com/watabou/pixeldungeon/items/TomeOfMastery.java b/src/com/watabou/pixeldungeon/items/TomeOfMastery.java
new file mode 100644
index 0000000000..6b27c0ba18
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/TomeOfMastery.java
@@ -0,0 +1,145 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.actors.buffs.Blindness;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Fury;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroSubClass;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.effects.SpellSprite;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.pixeldungeon.windows.WndChooseWay;
+
+public class TomeOfMastery extends Item {
+
+ private static final String TXT_BLINDED = "You can't read while blinded";
+
+ public static final float TIME_TO_READ = 10;
+
+ public static final String AC_READ = "READ";
+
+ {
+ stackable = false;
+ name = "Tome of Mastery";
+ image = ItemSpriteSheet.MASTERY;
+
+ unique = true;
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( AC_READ );
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+ if (action.equals( AC_READ )) {
+
+ if (hero.buff( Blindness.class ) != null) {
+ GLog.w( TXT_BLINDED );
+ return;
+ }
+
+ curUser = hero;
+
+ HeroSubClass way1 = null;
+ HeroSubClass way2 = null;
+ switch (hero.heroClass) {
+ case WARRIOR:
+ way1 = HeroSubClass.GLADIATOR;
+ way2 = HeroSubClass.BERSERKER;
+ break;
+ case MAGE:
+ way1 = HeroSubClass.BATTLEMAGE;
+ way2 = HeroSubClass.WARLOCK;
+ break;
+ case ROGUE:
+ way1 = HeroSubClass.FREERUNNER;
+ way2 = HeroSubClass.ASSASSIN;
+ break;
+ case HUNTRESS:
+ way1 = HeroSubClass.SNIPER;
+ way2 = HeroSubClass.WARDEN;
+ break;
+ }
+ GameScene.show( new WndChooseWay( this, way1, way2 ) );
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ @Override
+ public boolean doPickUp( Hero hero ) {
+ Badges.validateMastery();
+ return super.doPickUp( hero );
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ @Override
+ public String info() {
+ return
+ "This worn leather book is not that thick, but you feel somehow, " +
+ "that you can gather a lot from it. Remember though that reading " +
+ "this tome may require some time.";
+ }
+
+ public void choose( HeroSubClass way ) {
+
+ detach( curUser.belongings.backpack );
+
+ curUser.spend( TomeOfMastery.TIME_TO_READ );
+ curUser.busy();
+
+ curUser.subClass = way;
+
+ curUser.sprite.operate( curUser.pos );
+ Sample.INSTANCE.play( Assets.SND_MASTERY );
+
+ SpellSprite.show( curUser, SpellSprite.MASTERY );
+ curUser.sprite.emitter().burst( Speck.factory( Speck.MASTERY ), 12 );
+ GLog.w( "You have chosen the way of the %s!", Utils.capitalize( way.title() ) );
+
+ if (way == HeroSubClass.BERSERKER && curUser.HP <= curUser.HT * Fury.LEVEL) {
+ Buff.affect( curUser, Fury.class );
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/Torch.java b/src/com/watabou/pixeldungeon/items/Torch.java
new file mode 100644
index 0000000000..d4b2104a21
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/Torch.java
@@ -0,0 +1,94 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items;
+
+import java.util.ArrayList;
+
+import com.watabou.noosa.particles.Emitter;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Light;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.effects.particles.FlameParticle;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+public class Torch extends Item {
+
+ public static final String AC_LIGHT = "LIGHT";
+
+ public static final float TIME_TO_LIGHT = 1;
+
+ {
+ name = "torch";
+ image = ItemSpriteSheet.TORCH;
+
+ stackable = true;
+
+ defaultAction = AC_LIGHT;
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( AC_LIGHT );
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+
+ if (action == AC_LIGHT) {
+
+ hero.spend( TIME_TO_LIGHT );
+ hero.busy();
+
+ hero.sprite.operate( hero.pos );
+
+ detach( hero.belongings.backpack );
+ Buff.affect( hero, Light.class, Light.DURATION );
+
+ Emitter emitter = hero.sprite.centerEmitter();
+ emitter.start( FlameParticle.FACTORY, 0.2f, 3 );
+
+ } else {
+
+ super.execute( hero, action );
+
+ }
+ }
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ @Override
+ public int price() {
+ return 10 * quantity;
+ }
+
+ @Override
+ public String info() {
+ return
+ "It's an indispensable item in The Demon Halls, which are notorious for their poor ambient lighting.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/Armor.java b/src/com/watabou/pixeldungeon/items/armor/Armor.java
new file mode 100644
index 0000000000..2bebad28ab
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/Armor.java
@@ -0,0 +1,376 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import java.util.ArrayList;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.EquipableItem;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.armor.glyphs.*;
+import com.watabou.pixeldungeon.sprites.HeroSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Armor extends EquipableItem {
+
+ private static final String TXT_EQUIP_CURSED = "your %s constricts around you painfully";
+ private static final String TXT_UNEQUIP_CURSED = "You can't remove cursed %s!";
+
+ private static final String TXT_IDENTIFY = "you are now familiar enough with your %s to identify it. It is %s.";
+
+ private static final String TXT_TO_STRING = "%s :%d";
+
+ private static final String TXT_INCOMPATIBLE =
+ "Interaction of different types of magic has erased the glyph on this armor!";
+
+ public int tier;
+
+ public int STR;
+ public int DR;
+
+ private int hitsToKnow = 10;
+
+ public Glyph glyph;
+
+ public Armor( int tier ) {
+
+ this.tier = tier;
+
+ STR = typicalSTR();
+ DR = typicalDR();
+ }
+
+ private static final String GLYPH = "glyph";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( GLYPH, glyph );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ glyph = (Glyph)bundle.get( GLYPH );
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ actions.add( isEquipped( hero ) ? AC_UNEQUIP : AC_EQUIP );
+ return actions;
+ }
+
+ @Override
+ public boolean doEquip( Hero hero ) {
+
+ detach( hero.belongings.backpack );
+
+ if (hero.belongings.armor == null || hero.belongings.armor.doUnequip( hero, true )) {
+
+ hero.belongings.armor = this;
+
+ cursedKnown = true;
+ if (cursed) {
+ equipCursed( hero );
+ GLog.n( TXT_EQUIP_CURSED, toString() );
+ }
+
+ ((HeroSprite)hero.sprite).updateArmor();
+
+ hero.spendAndNext( 2 * hero.speed() );
+ return true;
+
+ } else {
+
+ collect( hero.belongings.backpack );
+ return false;
+
+ }
+ }
+
+ @Override
+ public boolean doUnequip( Hero hero, boolean collect ) {
+ if (cursed) {
+
+ GLog.w( TXT_UNEQUIP_CURSED, name() );
+ return false;
+
+ } else {
+
+ hero.belongings.armor = null;
+ hero.spendAndNext( hero.speed() );
+
+ ((HeroSprite)hero.sprite).updateArmor();
+
+ if (collect && !collect( hero.belongings.backpack )) {
+ Dungeon.level.drop( this, hero.pos );
+ }
+
+ return true;
+
+ }
+ }
+
+ @Override
+ public boolean isEquipped( Hero hero ) {
+ return hero.belongings.armor == this;
+ }
+
+ @Override
+ public Item upgrade() {
+ return upgrade( false );
+ }
+
+ public Item upgrade( boolean inscribe ) {
+
+ if (glyph != null) {
+ if (!inscribe && Random.Int( level ) > 0) {
+ GLog.w( TXT_INCOMPATIBLE );
+ inscribe( null );
+ }
+ } else {
+ if (inscribe) {
+ inscribe( Glyph.random() );
+ }
+ };
+
+ DR += tier;
+ STR--;
+
+ return super.upgrade();
+ }
+
+ public Item safeUpgrade() {
+ return upgrade( glyph != null );
+ }
+
+ @Override
+ public Item degrade() {
+ DR -= tier;
+ STR++;
+
+ return super.degrade();
+ }
+
+ public int proc( Char attacker, Char defender, int damage ) {
+
+ if (glyph != null) {
+ damage = glyph.proc( this, attacker, defender, damage );
+ }
+
+ if (!levelKnown) {
+ if (--hitsToKnow <= 0) {
+ levelKnown = true;
+ GLog.w( TXT_IDENTIFY, name(), toString() );
+ Badges.validateItemLevelAquired( this );
+ }
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String toString() {
+ return levelKnown ? Utils.format( TXT_TO_STRING, super.toString(), STR ) : super.toString();
+ }
+
+ @Override
+ public String name() {
+ return glyph == null ? super.name() : glyph.name( super.name() );
+ }
+
+ @Override
+ public String info() {
+ String name = name();
+ StringBuilder info = new StringBuilder( desc() );
+
+ if (levelKnown) {
+ info.append(
+ "\n\nThis " + name + " provides damage absorption up to " +
+ "" + Math.max( DR, 0 ) + " points per attack. " );
+
+ if (STR > Dungeon.hero.STR()) {
+
+ if (isEquipped( Dungeon.hero )) {
+ info.append(
+ "\n\nBecause of your inadequate strength your " +
+ "movement speed and defense skill is decreased. " );
+ } else {
+ info.append(
+ "\n\nBecause of your inadequate strength wearing this armor " +
+ "will decrease your movement speed and defense skill. " );
+ }
+
+ }
+ } else {
+ info.append(
+ "\n\nTypical " + name + " provides damage absorption up to " + typicalDR() + " points per attack " +
+ " and requires " + typicalSTR() + " points of strength. " );
+ if (typicalSTR() > Dungeon.hero.STR()) {
+ info.append( "Probably this armor is too heavy for you. " );
+ }
+ }
+
+ if (glyph != null) {
+ info.append( "It is inscribed." );
+ }
+
+ if (isEquipped( Dungeon.hero )) {
+ info.append( "\n\nYou are wearing the " + name +
+ (cursed ? ", and because it is cursed, you are powerless to remove it." : ".") );
+ } else {
+ if (cursedKnown && cursed) {
+ info.append( "\n\nYou can feel a malevolent magic lurking within the " + name + "." );
+ }
+ }
+
+ return info.toString();
+ }
+
+ @Override
+ public Item random() {
+ if (Random.Float() < 0.4) {
+ int n = 1;
+ if (Random.Int( 3 ) == 0) {
+ n++;
+ if (Random.Int( 3 ) == 0) {
+ n++;
+ }
+ }
+ if (Random.Int( 2 ) == 0) {
+ upgrade( n );
+ } else {
+ degrade( n );
+ cursed = true;
+ }
+ }
+
+ if (Random.Int( 10 ) == 0) {
+ inscribe( Glyph.random() );
+ }
+
+ return this;
+ }
+
+ public int typicalSTR() {
+ return 7 + tier * 2;
+ }
+
+ public int typicalDR() {
+ return tier * 2;
+ }
+
+ @Override
+ public int price() {
+ int price = 10 * (1 << (tier - 1));
+ if (glyph != null) {
+ price *= 1.5;
+ }
+ if (cursed && cursedKnown) {
+ price /= 2;
+ }
+ if (levelKnown) {
+ if (level > 0) {
+ price *= (level + 1);
+ } else if (level < 0) {
+ price /= (1 - level);
+ }
+ }
+ if (price < 1) {
+ price = 1;
+ }
+ return price;
+ }
+
+ public Armor inscribe( Glyph glyph ) {
+ this.glyph = glyph;
+ return this;
+ }
+
+ public boolean isInscribed() {
+ return glyph != null;
+ }
+
+ @Override
+ public ItemSprite.Glowing glowing() {
+ return glyph != null ? glyph.glowing() : null;
+ }
+
+ public static abstract class Glyph implements Bundlable {
+
+ private static final Class>[] glyphs = new Class>[]{
+ Bounce.class, Affection.class, AntiEntropy.class, Multiplicity.class,
+ Potential.class, Metabolism.class, Stench.class, Viscosity.class,
+ Displacement.class, Entanglement.class };
+
+ private static final float[] chances= new float[]{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
+
+ public abstract int proc( Armor armor, Char attacker, Char defender, int damage );
+
+ public String name() {
+ return name( "glyph" );
+ }
+
+ public String name( String armorName ) {
+ return armorName;
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ }
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ }
+
+ public ItemSprite.Glowing glowing() {
+ return ItemSprite.Glowing.WHITE;
+ }
+
+ public boolean checkOwner( Char owner ) {
+ if (!owner.isAlive() && owner instanceof Hero) {
+
+ ((Hero)owner).killerGlyph = this;
+ Badges.validateDeathFromGlyph();
+ return true;
+
+ } else {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Glyph random() {
+ try {
+ return ((Class)glyphs[ Random.chances( chances ) ]).newInstance();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/ClassArmor.java b/src/com/watabou/pixeldungeon/items/armor/ClassArmor.java
new file mode 100644
index 0000000000..0baebc821e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/ClassArmor.java
@@ -0,0 +1,134 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import java.util.ArrayList;
+
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Bundle;
+
+abstract public class ClassArmor extends Armor {
+
+ private static final String TXT_LOW_HEALTH = "Your health is too low!";
+ private static final String TXT_NOT_EQUIPPED = "You need to be wearing this armor to use its special power!";
+
+ {
+ levelKnown = true;
+ cursedKnown = true;
+ defaultAction = special();
+ }
+
+ public ClassArmor() {
+ super( 6 );
+ }
+
+ public static ClassArmor upgrade ( Hero owner, Armor armor ) {
+
+ ClassArmor classArmor = null;
+
+ switch (owner.heroClass) {
+ case WARRIOR:
+ classArmor = new WarriorArmor();
+ break;
+ case ROGUE:
+ classArmor = new RogueArmor();
+ break;
+ case MAGE:
+ classArmor = new MageArmor();
+ break;
+ case HUNTRESS:
+ classArmor = new HuntressArmor();
+ break;
+ }
+
+ classArmor.STR = armor.STR;
+ classArmor.DR = armor.DR;
+
+ classArmor.inscribe( armor.glyph );
+
+ return classArmor;
+ }
+
+ private static final String ARMOR_STR = "STR";
+ private static final String ARMOR_DR = "DR";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( ARMOR_STR, STR );
+ bundle.put( ARMOR_DR, DR );
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ STR = bundle.getInt( ARMOR_STR );
+ DR = bundle.getInt( ARMOR_DR );
+ }
+
+ @Override
+ public ArrayList actions( Hero hero ) {
+ ArrayList actions = super.actions( hero );
+ if (hero.HP >= 2 && isEquipped( hero )) {
+ actions.add( special() );
+ }
+ return actions;
+ }
+
+ @Override
+ public void execute( Hero hero, String action ) {
+ if (action == special()) {
+
+ if (hero.HP < 2) {
+ GLog.w( TXT_LOW_HEALTH );
+ } else if (!isEquipped( hero )) {
+ GLog.w( TXT_NOT_EQUIPPED );
+ } else {
+ curUser = hero;
+ doSpecial();
+ }
+
+ } else {
+ super.execute( hero, action );
+ }
+ }
+
+ abstract public String special();
+ abstract public void doSpecial();
+
+ @Override
+ public boolean isUpgradable() {
+ return false;
+ }
+
+ @Override
+ public boolean isIdentified() {
+ return true;
+ }
+
+ @Override
+ public int price() {
+ return 0;
+ }
+
+ @Override
+ public String desc() {
+ return "The thing looks awesome!";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/ClothArmor.java b/src/com/watabou/pixeldungeon/items/armor/ClothArmor.java
new file mode 100644
index 0000000000..182bb0d42e
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/ClothArmor.java
@@ -0,0 +1,38 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+
+public class ClothArmor extends Armor {
+
+ {
+ name = "cloth armor";
+ image = ItemSpriteSheet.ARMOR_CLOTH;
+ }
+
+ public ClothArmor() {
+ super( 1 );
+ }
+
+ @Override
+ public String desc() {
+ return "This lightweight armor offers basic protection.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/HuntressArmor.java b/src/com/watabou/pixeldungeon/items/armor/HuntressArmor.java
new file mode 100644
index 0000000000..7135d7e5a6
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/HuntressArmor.java
@@ -0,0 +1,107 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import java.util.HashMap;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.items.weapon.missiles.Shuriken;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.sprites.MissileSprite;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.Callback;
+
+public class HuntressArmor extends ClassArmor {
+
+ private static final String TXT_NO_ENEMIES = "No enemies in sight";
+ private static final String TXT_NOT_HUNTRESS = "Only huntresses can use this armor!";
+
+ private static final String AC_SPECIAL = "SPECTRAL BLADES";
+
+ {
+ name = "huntress cloak";
+ image = ItemSpriteSheet.ARMOR_HUNTRESS;
+ }
+
+ private HashMap targets = new HashMap();
+
+ @Override
+ public String special() {
+ return AC_SPECIAL;
+ }
+
+ @Override
+ public void doSpecial() {
+
+ Item proto = new Shuriken();
+
+ for (Mob mob : Dungeon.level.mobs) {
+ if (Level.fieldOfView[mob.pos]) {
+
+ Callback callback = new Callback() {
+ @Override
+ public void call() {
+ curUser.attack( targets.get( this ) );
+ targets.remove( this );
+ if (targets.isEmpty()) {
+ curUser.spendAndNext( curUser.attackDelay() );
+ }
+ }
+ };
+
+ ((MissileSprite)curUser.sprite.parent.recycle( MissileSprite.class )).
+ reset( curUser.pos, mob.pos, proto, callback );
+
+ targets.put( callback, mob );
+ }
+ }
+
+ if (targets.size() == 0) {
+ GLog.w( TXT_NO_ENEMIES );
+ return;
+ }
+
+ curUser.HP /= 2;
+
+ curUser.sprite.zap( curUser.pos );
+ curUser.busy();
+ }
+
+ @Override
+ public boolean doEquip( Hero hero ) {
+ if (hero.heroClass == HeroClass.HUNTRESS) {
+ return super.doEquip( hero );
+ } else {
+ GLog.w( TXT_NOT_HUNTRESS );
+ return false;
+ }
+ }
+
+ @Override
+ public String desc() {
+ return
+ "A huntress in such cloak can create a fan of spectral blades. Each of these blades " +
+ "will target a single enemy in the huntress's field of view, inflicting damage depending " +
+ "on her currently equipped melee weapon.";
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/items/armor/LeatherArmor.java b/src/com/watabou/pixeldungeon/items/armor/LeatherArmor.java
new file mode 100644
index 0000000000..e4e53437ec
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/LeatherArmor.java
@@ -0,0 +1,38 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+
+public class LeatherArmor extends Armor {
+
+ {
+ name = "leather armor";
+ image = ItemSpriteSheet.ARMOR_LEATHER;
+ }
+
+ public LeatherArmor() {
+ super( 2 );
+ }
+
+ @Override
+ public String desc() {
+ return "Armor made from tanned monster hide. Not as light as cloth armor but provides better protection.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/MageArmor.java b/src/com/watabou/pixeldungeon/items/armor/MageArmor.java
new file mode 100644
index 0000000000..cadc05bf5f
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/MageArmor.java
@@ -0,0 +1,87 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.effects.particles.ElmoParticle;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+
+public class MageArmor extends ClassArmor {
+
+ private static final String AC_SPECIAL = "MOLTEN EARTH";
+
+ private static final String TXT_NOT_MAGE = "Only mages can use this armor!";
+
+ {
+ name = "mage robe";
+ image = ItemSpriteSheet.ARMOR_MAGE;
+ }
+
+ @Override
+ public String special() {
+ return AC_SPECIAL;
+ }
+
+ @Override
+ public String desc() {
+ return
+ "Wearing this gorgeous robe, a mage can cast a spell of molten earth: all the enemies " +
+ "in his field of view will be set on fire and unable to move at the same time.";
+ }
+
+ @Override
+ public void doSpecial() {
+
+ for (Mob mob : Dungeon.level.mobs) {
+ if (Level.fieldOfView[mob.pos]) {
+ Buff.affect( mob, Burning.class ).reignite( mob );
+ Buff.prolong( mob, Roots.class, 3 );
+ }
+ }
+
+ curUser.HP /= 2;
+
+ curUser.spend( Actor.TICK );
+ curUser.sprite.operate( curUser.pos );
+ curUser.busy();
+
+ curUser.sprite.centerEmitter().start( ElmoParticle.FACTORY, 0.15f, 4 );
+ Sample.INSTANCE.play( Assets.SND_READ );
+ }
+
+ @Override
+ public boolean doEquip( Hero hero ) {
+ if (hero.heroClass == HeroClass.MAGE) {
+ return super.doEquip( hero );
+ } else {
+ GLog.w( TXT_NOT_MAGE );
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/items/armor/MailArmor.java b/src/com/watabou/pixeldungeon/items/armor/MailArmor.java
new file mode 100644
index 0000000000..1039c5fff9
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/MailArmor.java
@@ -0,0 +1,39 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+
+public class MailArmor extends Armor {
+
+ {
+ name = "mail armor";
+ image = ItemSpriteSheet.ARMOR_MAIL;
+ }
+
+ public MailArmor() {
+ super( 3 );
+ }
+
+ @Override
+ public String desc() {
+ return
+ "Interlocking metal links make for a tough but flexible suit of armor.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/PlateArmor.java b/src/com/watabou/pixeldungeon/items/armor/PlateArmor.java
new file mode 100644
index 0000000000..c69822b61c
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/PlateArmor.java
@@ -0,0 +1,40 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+
+public class PlateArmor extends Armor {
+
+ {
+ name = "plate armor";
+ image = ItemSpriteSheet.ARMOR_PLATE;
+ }
+
+ public PlateArmor() {
+ super( 5 );
+ }
+
+ @Override
+ public String desc() {
+ return
+ "Enormous plates of metal are joined together into a suit that provides " +
+ "unmatched protection to any adventurer strong enough to bear its staggering weight.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/RogueArmor.java b/src/com/watabou/pixeldungeon/items/armor/RogueArmor.java
new file mode 100644
index 0000000000..fe75a1e851
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/RogueArmor.java
@@ -0,0 +1,117 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.noosa.audio.Sample;
+import com.watabou.pixeldungeon.Assets;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.buffs.Blindness;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.actors.mobs.Mob.State;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.CellSelector;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+
+public class RogueArmor extends ClassArmor {
+
+ private static final String TXT_FOV = "You can only jump to an empty location in your field of view";
+ private static final String TXT_NOT_ROGUE = "Only rogues can use this armor!";
+
+ private static final String AC_SPECIAL = "SMOKE BOMB";
+
+ {
+ name = "rogue garb";
+ image = ItemSpriteSheet.ARMOR_ROGUE;
+ }
+
+ @Override
+ public String special() {
+ return AC_SPECIAL;
+ }
+
+ @Override
+ public void doSpecial() {
+ GameScene.selectCell( teleporter );
+ }
+
+ @Override
+ public boolean doEquip( Hero hero ) {
+ if (hero.heroClass == HeroClass.ROGUE) {
+ return super.doEquip( hero );
+ } else {
+ GLog.w( TXT_NOT_ROGUE );
+ return false;
+ }
+ }
+
+ @Override
+ public String desc() {
+ return
+ "Wearing this dark garb, a rogue can perform a trick, that is called \"smoke bomb\" " +
+ "(though no real explosives are used): he blinds enemies who could see him and jumps aside.";
+ }
+
+ protected static CellSelector.Listener teleporter = new CellSelector.Listener() {
+
+ @Override
+ public void onSelect( Integer target ) {
+ if (target != null) {
+
+ if (!Level.fieldOfView[target] ||
+ !(Level.passable[target] || Level.avoid[target]) ||
+ Actor.findChar( target ) != null) {
+
+ GLog.w( TXT_FOV );
+ return;
+ }
+
+ curUser.HP /= 2;
+
+ for (Mob mob : Dungeon.level.mobs) {
+ if (Level.fieldOfView[mob.pos]) {
+ Buff.prolong( mob, Blindness.class, 2 );
+ mob.state = State.WANDERING;
+ mob.sprite.emitter().burst( Speck.factory( Speck.LIGHT ), 4 );
+ }
+ }
+
+ WandOfBlink.appear( curUser, target );
+ CellEmitter.get( target ).burst( Speck.factory( Speck.WOOL ), 10 );
+ Sample.INSTANCE.play( Assets.SND_PUFF );
+ Dungeon.level.press( target, curUser );
+ Dungeon.observe();
+
+ curUser.spendAndNext( Actor.TICK );
+ }
+ }
+
+ @Override
+ public String prompt() {
+ return "Choose a location to jump to";
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/items/armor/ScaleArmor.java b/src/com/watabou/pixeldungeon/items/armor/ScaleArmor.java
new file mode 100644
index 0000000000..5d46e31c96
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/ScaleArmor.java
@@ -0,0 +1,39 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+
+
+public class ScaleArmor extends Armor {
+
+ {
+ name = "scale armor";
+ image = ItemSpriteSheet.ARMOR_SCALE;
+ }
+
+ public ScaleArmor() {
+ super( 4 );
+ }
+
+ @Override
+ public String desc() {
+ return
+ "The metal scales sewn onto a leather vest create a flexible, yet protective armor.";
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/WarriorArmor.java b/src/com/watabou/pixeldungeon/items/armor/WarriorArmor.java
new file mode 100644
index 0000000000..8988a8d668
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/WarriorArmor.java
@@ -0,0 +1,129 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor;
+
+import com.watabou.noosa.Camera;
+import com.watabou.noosa.tweeners.PosTweener;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Fury;
+import com.watabou.pixeldungeon.actors.buffs.Invisibility;
+import com.watabou.pixeldungeon.actors.buffs.Paralysis;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.hero.HeroClass;
+import com.watabou.pixeldungeon.actors.hero.HeroSubClass;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.mechanics.Ballistica;
+import com.watabou.pixeldungeon.scenes.CellSelector;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSpriteSheet;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.utils.PointF;
+
+public class WarriorArmor extends ClassArmor {
+
+ private static int LEAP_TIME = 1;
+ private static int SHOCK_TIME = 3;
+
+ private static final String AC_SPECIAL = "HEROIC LEAP";
+
+ private static final String TXT_NOT_WARRIOR = "Only warriors can use this armor!";
+
+ {
+ name = "warrior suit of armor";
+ image = ItemSpriteSheet.ARMOR_WARRIOR;
+ }
+
+ @Override
+ public String special() {
+ return AC_SPECIAL;
+ }
+
+ @Override
+ public void doSpecial() {
+ GameScene.selectCell( leaper );
+ }
+
+ @Override
+ public boolean doEquip( Hero hero ) {
+ if (hero.heroClass == HeroClass.WARRIOR) {
+ return super.doEquip( hero );
+ } else {
+ GLog.w( TXT_NOT_WARRIOR );
+ return false;
+ }
+ }
+
+ @Override
+ public String desc() {
+ return
+ "While this armor looks heavy, it allows a warrior to perform heroic leap towards " +
+ "a targeted location, slamming down to stun all neighbouring enemies.";
+ }
+
+ protected static CellSelector.Listener leaper = new CellSelector.Listener() {
+
+ @Override
+ public void onSelect( Integer target ) {
+ if (target != null && target != curUser.pos) {
+
+ int cell = Ballistica.cast( curUser.pos, target, false, true );
+ if (Actor.findChar( cell ) != null && cell != curUser.pos) {
+ cell = Ballistica.trace[Ballistica.distance - 2];
+ }
+
+ curUser.HP /= 2;
+ if (curUser.subClass == HeroSubClass.BERSERKER && curUser.HP <= curUser.HT * Fury.LEVEL) {
+ Buff.affect( curUser, Fury.class );
+ }
+
+ Invisibility.dispel();
+
+ curUser.move( cell );
+ curUser.sprite.place( cell );
+ Dungeon.level.press( target, curUser );
+ Dungeon.observe();
+
+ for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
+ Char mob = Actor.findChar( curUser.pos + Level.NEIGHBOURS8[i] );
+ if (mob != null && mob != curUser) {
+ Buff.prolong( mob, Paralysis.class, SHOCK_TIME );
+ }
+ }
+
+ PointF pos = curUser.sprite.point();
+ Camera.main.target = null;
+ curUser.sprite.y -= 16;
+ curUser.sprite.parent.add( new PosTweener( curUser.sprite, pos, 0.1f ) );
+
+ CellEmitter.center( cell ).burst( Speck.factory( Speck.DUST ), 10 );
+
+ curUser.spendAndNext( LEAP_TIME );
+ }
+ }
+
+ @Override
+ public String prompt() {
+ return "Choose direction to leap";
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Affection.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Affection.java
new file mode 100644
index 0000000000..7e62d96538
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Affection.java
@@ -0,0 +1,66 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Charm;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.GameMath;
+import com.watabou.utils.Random;
+
+public class Affection extends Glyph {
+
+ private static final String TXT_AFFECTION = "%s of affection";
+
+ private static ItemSprite.Glowing PINK = new ItemSprite.Glowing( 0xFF4488 );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = (int)GameMath.gate( 0, armor.level, 6 );
+
+ if (Level.adjacent( attacker.pos, defender.pos ) && Random.Int( level / 2 + 5 ) >= 4) {
+
+ int duration = Random.IntRange( 2, 5 );
+
+ Buff.affect( attacker, Charm.class, Charm.durationFactor( attacker ) * duration );
+ attacker.sprite.centerEmitter().start( Speck.factory( Speck.HEART ), 0.2f, 5 );
+
+ Buff.affect( defender, Charm.class, Random.Float( Charm.durationFactor( defender ) * duration / 2, duration ) );
+ defender.sprite.centerEmitter().start( Speck.factory( Speck.HEART ), 0.2f, 5 );
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_AFFECTION, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return PINK;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/AntiEntropy.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/AntiEntropy.java
new file mode 100644
index 0000000000..86a4283a15
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/AntiEntropy.java
@@ -0,0 +1,67 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Burning;
+import com.watabou.pixeldungeon.actors.buffs.Frost;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.particles.FlameParticle;
+import com.watabou.pixeldungeon.effects.particles.SnowParticle;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.Random;
+
+public class AntiEntropy extends Glyph {
+
+ private static final String TXT_ANTI_ENTROPY = "%s of anti-entropy";
+
+ private static ItemSprite.Glowing BLUE = new ItemSprite.Glowing( 0x0000FF );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = Math.max( 0, armor.level );
+
+ if (Level.adjacent( attacker.pos, defender.pos ) && Random.Int( level + 6 ) >= 5) {
+
+ Buff.prolong( attacker, Frost.class, Frost.duration( attacker ) * Random.Float( 1f, 1.5f ));
+ CellEmitter.get( attacker.pos ).start( SnowParticle.FACTORY, 0.2f, 6 );
+
+ Buff.affect( defender, Burning.class ).reignite( defender );
+ defender.sprite.emitter().burst( FlameParticle.FACTORY, 5 );
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_ANTI_ENTROPY, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return BLUE;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Bounce.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Bounce.java
new file mode 100644
index 0000000000..241da599b8
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Bounce.java
@@ -0,0 +1,72 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.mobs.Mob;
+import com.watabou.pixeldungeon.effects.Pushing;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.utils.Random;
+
+public class Bounce extends Glyph {
+
+ private static final String TXT_BOUNCE = "%s of bounce";
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = Math.max( 0, armor.level );
+
+ if (Level.adjacent( attacker.pos, defender.pos ) && Random.Int( level + 5) >= 4) {
+
+ for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
+ int ofs = Level.NEIGHBOURS8[i];
+ if (attacker.pos - defender.pos == ofs) {
+ int newPos = attacker.pos + ofs;
+ if ((Level.passable[newPos] || Level.avoid[newPos]) && Actor.findChar( newPos ) == null) {
+
+ Actor.addDelayed( new Pushing( attacker, attacker.pos, newPos ), -1 );
+
+ attacker.pos = newPos;
+ // Âîò ýòî âîò êðèâî :(
+ if (attacker instanceof Mob) {
+ Dungeon.level.mobPress( (Mob)attacker );
+ } else {
+ Dungeon.level.press( newPos, attacker );
+ }
+
+ }
+ break;
+ }
+ }
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_BOUNCE, weaponName );
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Displacement.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Displacement.java
new file mode 100644
index 0000000000..6174244272
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Displacement.java
@@ -0,0 +1,69 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.Random;
+
+public class Displacement extends Glyph {
+
+ private static final String TXT_DISPLACEMENT = "%s of displacement";
+
+ private static ItemSprite.Glowing BLUE = new ItemSprite.Glowing( 0x66AAFF );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage ) {
+
+ if (Dungeon.bossLevel()) {
+ return damage;
+ }
+
+ int nTries = (armor.level < 0 ? 1 : armor.level + 1) * 5;
+ for (int i=0; i < nTries; i++) {
+ int pos = Random.Int( Level.LENGTH );
+ if (Dungeon.visible[pos] && Level.passable[pos] && Actor.findChar( pos ) == null) {
+
+ WandOfBlink.appear( defender, pos );
+ Dungeon.level.press( pos, defender );
+ Dungeon.observe();
+
+ break;
+ }
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_DISPLACEMENT, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return BLUE;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Entanglement.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Entanglement.java
new file mode 100644
index 0000000000..e41652b354
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Entanglement.java
@@ -0,0 +1,66 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.noosa.Camera;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.actors.buffs.Roots;
+import com.watabou.pixeldungeon.effects.CellEmitter;
+import com.watabou.pixeldungeon.effects.particles.EarthParticle;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.plants.Earthroot;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.Random;
+
+public class Entanglement extends Glyph {
+
+ private static final String TXT_ENTANGLEMENT = "%s of entanglement";
+
+ private static ItemSprite.Glowing GREEN = new ItemSprite.Glowing( 0x448822 );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage ) {
+
+ int level = Math.max( 0, armor.level );
+
+ if (Random.Int( 4 ) == 0) {
+
+ Buff.prolong( defender, Roots.class, 5 - level / 5 );
+ Buff.affect( defender, Earthroot.Armor.class ).level( 5 * (level + 1) );
+ CellEmitter.bottom( defender.pos ).start( EarthParticle.FACTORY, 0.05f, 8 );
+ Camera.main.shake( 1, 0.4f );
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_ENTANGLEMENT, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return GREEN;
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Metabolism.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Metabolism.java
new file mode 100644
index 0000000000..7555f231f1
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Metabolism.java
@@ -0,0 +1,74 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Hunger;
+import com.watabou.pixeldungeon.effects.Speck;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.utils.Random;
+
+public class Metabolism extends Glyph {
+
+ private static final String TXT_METABOLISM = "%s of metabolism";
+
+ private static ItemSprite.Glowing RED = new ItemSprite.Glowing( 0xCC0000 );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = Math.max( 0, armor.level );
+ if (Random.Int( level / 2 + 5 ) >= 4) {
+
+ int healing = Math.min( defender.HT - defender.HP, Random.Int( 1, defender.HT / 5 ) );
+
+ if (healing > 0) {
+
+ Hunger hunger = defender.buff( Hunger.class );
+
+ if (hunger != null && !hunger.isStarving()) {
+
+ hunger.satisfy( -Hunger.STARVING / 10 );
+ BuffIndicator.refreshHero();
+
+ defender.HP += healing;
+ defender.sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 );
+ defender.sprite.showStatus( CharSprite.POSITIVE, Integer.toString( healing ) );
+ }
+ }
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_METABOLISM, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return RED;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Multiplicity.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Multiplicity.java
new file mode 100644
index 0000000000..9ce857fc73
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Multiplicity.java
@@ -0,0 +1,81 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import java.util.ArrayList;
+
+import com.watabou.pixeldungeon.actors.Actor;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.actors.mobs.npcs.MirrorImage;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.items.wands.WandOfBlink;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.Random;
+
+public class Multiplicity extends Glyph {
+
+ private static final String TXT_MULTIPLICITY = "%s of multiplicity";
+
+ private static ItemSprite.Glowing PINK = new ItemSprite.Glowing( 0xCCAA88 );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = Math.max( 0, armor.level );
+
+ if (Random.Int( level / 2 + 6 ) >= 5) {
+
+ ArrayList respawnPoints = new ArrayList();
+
+ for (int i=0; i < Level.NEIGHBOURS8.length; i++) {
+ int p = defender.pos + Level.NEIGHBOURS8[i];
+ if (Actor.findChar( p ) == null && (Level.passable[p] || Level.avoid[p])) {
+ respawnPoints.add( p );
+ }
+ }
+
+ if (respawnPoints.size() > 0) {
+ MirrorImage mob = new MirrorImage();
+ mob.duplicate( (Hero)defender );
+ GameScene.add( mob );
+ WandOfBlink.appear( mob, Random.element( respawnPoints ) );
+
+ defender.damage( Random.IntRange( 1, defender.HT / 6 ), /*attacker*/ this );
+ checkOwner( defender );
+ }
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_MULTIPLICITY, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return PINK;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Potential.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Potential.java
new file mode 100644
index 0000000000..331cfb5492
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Potential.java
@@ -0,0 +1,72 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.noosa.Camera;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.effects.Lightning;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.levels.traps.LightningTrap;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.Random;
+
+public class Potential extends Glyph {
+
+ private static final String TXT_POTENTIAL = "%s of potential";
+
+ private static ItemSprite.Glowing BLUE = new ItemSprite.Glowing( 0x66CCEE );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = Math.max( 0, armor.level );
+
+ if (Level.adjacent( attacker.pos, defender.pos ) && Random.Int( level + 7 ) >= 6) {
+
+ int dmg = Random.IntRange( 1, damage );
+ attacker.damage( dmg, LightningTrap.LIGHTNING );
+ dmg = Random.IntRange( 1, dmg );
+ defender.damage( dmg, LightningTrap.LIGHTNING );
+
+ checkOwner( defender );
+ if (defender == Dungeon.hero) {
+ Camera.main.shake( 2, 0.3f );
+ }
+
+ int[] points = {attacker.pos, defender.pos};
+ attacker.sprite.parent.add( new Lightning( points, 2, null ) );
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_POTENTIAL, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return BLUE;
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Stench.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Stench.java
new file mode 100644
index 0000000000..af2e29a938
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Stench.java
@@ -0,0 +1,61 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.blobs.Blob;
+import com.watabou.pixeldungeon.actors.blobs.ToxicGas;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.levels.Level;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.utils.Random;
+
+public class Stench extends Glyph {
+
+ private static final String TXT_STENCH = "%s of stench";
+
+ private static ItemSprite.Glowing GREEN = new ItemSprite.Glowing( 0x22CC44 );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage) {
+
+ int level = Math.max( 0, armor.level );
+
+ if (Level.adjacent( attacker.pos, defender.pos ) && Random.Int( level + 5 ) >= 4) {
+
+ GameScene.add( Blob.seed( attacker.pos, 20, ToxicGas.class ) );
+
+ }
+
+ return damage;
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_STENCH, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return GREEN;
+ }
+
+}
diff --git a/src/com/watabou/pixeldungeon/items/armor/glyphs/Viscosity.java b/src/com/watabou/pixeldungeon/items/armor/glyphs/Viscosity.java
new file mode 100644
index 0000000000..ae8253d55d
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/armor/glyphs/Viscosity.java
@@ -0,0 +1,150 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.armor.glyphs;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.Dungeon;
+import com.watabou.pixeldungeon.ResultDescriptions;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.buffs.Buff;
+import com.watabou.pixeldungeon.items.armor.Armor;
+import com.watabou.pixeldungeon.items.armor.Armor.Glyph;
+import com.watabou.pixeldungeon.sprites.CharSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite;
+import com.watabou.pixeldungeon.sprites.ItemSprite.Glowing;
+import com.watabou.pixeldungeon.ui.BuffIndicator;
+import com.watabou.pixeldungeon.utils.GLog;
+import com.watabou.pixeldungeon.utils.Utils;
+import com.watabou.utils.Bundle;
+import com.watabou.utils.Random;
+
+public class Viscosity extends Glyph {
+
+ private static final String TXT_VISCOSITY = "%s of viscosity";
+
+ private static ItemSprite.Glowing PURPLE = new ItemSprite.Glowing( 0x8844CC );
+
+ @Override
+ public int proc( Armor armor, Char attacker, Char defender, int damage ) {
+
+ if (damage == 0) {
+ return 0;
+ }
+
+ int level = Math.max( 0, armor.level );
+
+ if (Random.Int( level + 7 ) >= 6) {
+
+ DeferedDamage debuff = defender.buff( DeferedDamage.class );
+ if (debuff == null) {
+ debuff = new DeferedDamage();
+ debuff.attachTo( defender );
+ }
+ debuff.prolong( damage );
+
+ defender.sprite.showStatus( CharSprite.WARNING, "deferred %d", damage );
+
+ return 0;
+
+ } else {
+ return damage;
+ }
+ }
+
+ @Override
+ public String name( String weaponName) {
+ return String.format( TXT_VISCOSITY, weaponName );
+ }
+
+ @Override
+ public Glowing glowing() {
+ return PURPLE;
+ }
+
+ public static class DeferedDamage extends Buff {
+
+ protected int damage = 0;
+
+ private static final String DAMAGE = "damage";
+
+ @Override
+ public void storeInBundle( Bundle bundle ) {
+ super.storeInBundle( bundle );
+ bundle.put( DAMAGE, damage );
+
+ }
+
+ @Override
+ public void restoreFromBundle( Bundle bundle ) {
+ super.restoreFromBundle( bundle );
+ damage = bundle.getInt( DAMAGE );
+ }
+
+ @Override
+ public boolean attachTo( Char target ) {
+ if (super.attachTo( target )) {
+ postpone( TICK );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void prolong( int damage ) {
+ this.damage += damage;
+ };
+
+ @Override
+ public int icon() {
+ return BuffIndicator.DEFERRED;
+ }
+
+ @Override
+ public String toString() {
+ return Utils.format( "Defered damage (%d)", damage );
+ }
+
+ @Override
+ public boolean act() {
+ if (target.isAlive()) {
+
+ target.damage( 1, this );
+ if (target == Dungeon.hero && !target.isAlive()) {
+ // Refactoring needed!
+ Glyph glyph = new Viscosity();
+ Dungeon.fail( Utils.format( ResultDescriptions.GLYPH, glyph.name(), Dungeon.depth ) );
+ GLog.n( "%s killed you...", glyph.name() );
+
+ Badges.validateDeathFromGlyph();
+ }
+ spend( TICK );
+
+ if (--damage <= 0) {
+ detach();
+ }
+
+ } else {
+
+ detach();
+
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/com/watabou/pixeldungeon/items/bags/Bag.java b/src/com/watabou/pixeldungeon/items/bags/Bag.java
new file mode 100644
index 0000000000..fcd2d2e21a
--- /dev/null
+++ b/src/com/watabou/pixeldungeon/items/bags/Bag.java
@@ -0,0 +1,186 @@
+/*
+ * Pixel Dungeon
+ * Copyright (C) 2012-2014 Oleg Dolya
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */
+package com.watabou.pixeldungeon.items.bags;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.watabou.pixeldungeon.Badges;
+import com.watabou.pixeldungeon.actors.Char;
+import com.watabou.pixeldungeon.actors.hero.Hero;
+import com.watabou.pixeldungeon.items.Item;
+import com.watabou.pixeldungeon.scenes.GameScene;
+import com.watabou.pixeldungeon.windows.WndBag;
+import com.watabou.utils.Bundlable;
+import com.watabou.utils.Bundle;
+
+public class Bag extends Item implements Iterable
- {
+
+ public static final String AC_OPEN = "OPEN";
+
+ {
+ image = 11;
+
+ defaultAction = AC_OPEN;
+ }
+
+ public Char owner;
+
+ public ArrayList
- items = new ArrayList