diff --git a/README.md b/README.md
index 482cdba..591c89b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,11 @@
# Spooky
Halloween plugin.
-
+A new bossfight's waiting for you and your players. The GHost is here .
+Build a structure like this one: (be sure to place the pumpkin in last!)
+
+
+
+Afterwards let the boss fight go!
Music used here: NIVIRO - The Return
Track's link: https://soundcloud.com/djniviro/thereturn
diff --git a/src/Ad5001/Spooky/Main.php b/src/Ad5001/Spooky/Main.php
index b8c7cbe..183d621 100644
--- a/src/Ad5001/Spooky/Main.php
+++ b/src/Ad5001/Spooky/Main.php
@@ -5,7 +5,10 @@ use pocketmine\command\Command;
use pocketmine\plugin\PluginBase;
use pocketmine\Server;
use pocketmine\Player;
+use pocketmine\event\Listener;
use pocketmine\item\enchantment\Enchantment;
+use pocketmine\event\block\BlockPlaceEvent;
+use pocketmine\block\Block;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\CompoundTag;
@@ -18,7 +21,7 @@ use Ad5001\Spooky\entity\Ghost;
use Ad5001\Spooky\tasks\TickTask;
-class Main extends PluginBase{
+class Main extends PluginBase implements Listener{
public $ghosts = [];
@@ -29,8 +32,9 @@ class Main extends PluginBase{
*/
public function onEnable(){
// Registering some enchants
- Enchantement::registerEnchantment(new Enchantement(Enchantement::SHARPNESS, "%enchantment.attack.sharpness", Enchantement::RARITY_COMMON, Enchantement::SLOT_SWORD));
- // $this->getServer()->getScheduler()->schedulerTask(new Task1($this), );
+ Enchantment::registerEnchantment(new Enchantment(Enchantment::SHARPNESS, "%enchantment.attack.sharpness", Enchantment::ACTIVATION_HELD, Enchantment::RARITY_COMMON, Enchantment::SLOT_SWORD));
+ $this->getServer()->getScheduler()->scheduleRepeatingTask(new TickTask($this), 2);
+ $this->getServer()->getPluginManager()->registerEvents($this, $this);
}
/**
@@ -51,7 +55,57 @@ class Main extends PluginBase{
}
+ public function onBlockPlace(BlockPlaceEvent $event) {
+ // Checking the pumpkin at the top
+ $found = false;
+ if($event->getBlock()->getId() == Block::PUMPKIN){
+ $under = $event->getBlock()->asVector3();
+ $under->y--;
+ // Hay bale for the body
+ if($event->getBlock()->getLevel()->getBlock($under)->getId() == Block::HAY_BALE) {
+ $under2 = $event->getBlock()->asVector3();
+ $under2->y--;
+ // Fence for the bottom
+ if($event->getBlock()->getLevel()->getBlock($under2)->getId() == Block::FENCE){
+ // Fences for the sides.
+ $side1 = $under->asVector3();
+ $side1->x++;
+ $side2 = $under->asVector3();
+ $side2->x--;
+ if($event->getBlock()->getLevel()->getBlock($side1)->getId() == Block::FENCE && $event->getBlock()->getLevel()->getBlock($side1)->getId() == Block::FENCE) {
+ $found = true;
+ } else {
+ $side1 = $under->asVector3();
+ $side1->z++;
+ $side2 = $under->asVector3();
+ $side2->z--;
+ if($event->getBlock()->getLevel()->getBlock($side1)->getId() == Block::FENCE && $event->getBlock()->getLevel()->getBlock($side1)->getId() == Block::FENCE) {
+ $found = true;
+ }
+ }
+ }
+ }
+ }
+ // If everything's right, we can destroy the structure & generate the ghost
+ if($found){
+ $event->setCancelled();
+ $event->getBlock()->getLevel()->setBlock($under, Block::get(Block::AIR));
+ $event->getBlock()->getLevel()->setBlock($under2, Block::get(Block::AIR));
+ $event->getBlock()->getLevel()->setBlock($side1, Block::get(Block::AIR));
+ $event->getBlock()->getLevel()->setBlock($side2, Block::get(Block::AIR));
+ if($event->getPlayer()){
+ $this->spawnGhost($event->getPlayer());
+ }
+ }
+ }
+
+ /**
+ * Spawns a ghost
+ *
+ * @param Player $p
+ * @return void
+ */
public function spawnGhost(Player $p){
// Getting the skin
$nbtSkin = new NBT(NBT::BIG_ENDIAN);
diff --git a/src/Ad5001/Spooky/entity/Ghost.php b/src/Ad5001/Spooky/entity/Ghost.php
index 380c14a..dcd8653 100644
--- a/src/Ad5001/Spooky/entity/Ghost.php
+++ b/src/Ad5001/Spooky/entity/Ghost.php
@@ -17,6 +17,7 @@ use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\ByteTag;
+use pocketmine\nbt\tag\StringTag;
use pocketmine\entity\Effect;
use Ad5001\Spooky\Main;
@@ -47,7 +48,7 @@ class Ghost extends Human {
$this->setDataProperty(self::DATA_SCALE, self::DATA_TYPE_FLOAT, new FloatTag("Scale", 1.2));
parent::__construct($level, $nbt);
$it = Item::get(Item::GOLDEN_HOE, 0);
- $it->addEnchantement(Enchantement::getEnchantement(Enchantement::SHARPNESS));
+ $it->addEnchantment(Enchantment::getEnchantment(Enchantment::SHARPNESS));
$this->getInventory()->setItemInHand($it);
}
@@ -97,6 +98,7 @@ class Ghost extends Human {
public function intenseFight(){
if(!$this->checkIfConnected()) return;
// TODO: Custom intense fight
+ $this->fightType = 1;
}
/**
@@ -118,7 +120,7 @@ class Ghost extends Human {
public function blackOutEnterPhase(){
if(!$this->checkIfConnected()) return;
$this->fightType = 0;
- $spawnBlock = $this->getPlayer()->getLineOfSight(2, 3, []);
+ $spawnBlock = $this->getPlayer()->getLineOfSight(2);
$spawnBlock = $spawnBlock[count($spawnBlock) -1];
$this->getPlayer()->addEffect(
Effect::getEffectById(Effect::BLINDNESS)->setDuration(30*20)->setAmplifier(4)->setVisible(false)
@@ -243,14 +245,18 @@ class Ghost extends Human {
/**
* Check the damage to reduce it by 25%
*
- * @param EntityDamageEvent $source
+ * @param EntityDamageEvent $event
*/
- public function attack(EntityDamageEvent $source) {
+ public function attack(EntityDamageEvent $event) {
if($event instanceof EntityDamageByEntityEvent) {
if($event->getDamager() instanceof Player && $event->getDamager()){
- $source->setDamage($source->getDamage() * 0.75);
+ $event->setDamage($event->getDamage() * 0.75);
} else {
$event->setCancelled(true);
+ $event->getDamager()->motionY = 12;
+ $event->getDamager()->addEffect(
+ Effect::getEffectById(Effect::NAUSEA)->setDuration(30*20)->setAmplifier(99)->setVisible(false)
+ );
}
}
}
@@ -262,19 +268,62 @@ class Ghost extends Human {
* @return bool
*/
public function onUpdate(int $currentTick): bool {
- if(!$this->checkIfConnected()) return;
+ if(!$this->checkIfConnected()) return false;
+ if($this->fightType == 0) return false;
+ // Teleportation
+ if(rand(0, 200) == 0) { // Do we do teleportation?
+ if(rand(0,1) == 0){ // Which kind of tp? Random around or forward?
+ $los = $this->getLineOfSight(10);
+ $b = $los[count($los) - 1];
+ $this->teleport($b);
+ } else {
+ $x = rand($this->x + 8, $this->x - 8);
+ $z = rand($this->z + 8, $this->z - 8);
+ for($y = $this->y + 16; $y > $this->y - 16; $this->y--){
+ if($this->getLevel()->getBlock($x, $y -1, $z)->getId() !== 0) break;
+ }
+ $this->teleport(new Vector3($x, $y, $z));
+ }
+ }
+ // Setting specific motion
$diffV3 = $this->associatedPlayer->asVector3()->subtract($this->asVector3());
$distDiff = $diffV3->asVector3()->abs();
$distDiff = $distDiff->x + $distDiff->z;
+ // Check if we can attack the player
if($this->distanceSquared($this->associatedPlayer) <= 1){
$this->attackEntity($this->associatedPlayer);
}
+ // If not, try moving torowards him
if ($diff > 0) {
- $this->motionX = $this->getSpeed() * 0.15 * ($x / $diff);
- $this->motionZ = $this->getSpeed() * 0.15 * ($z / $diff);
- $this->yaw = rad2deg(-atan2($x / $diff, $z / $diff));
+ $this->motionX = $this->getSpeed() * 0.15 * ($diffV3->x / $distDiff);
+ $this->motionZ = $this->getSpeed() * 0.15 * ($diffV3->z / $distDiff);
+ $this->yaw = rad2deg(-atan2($diffV3->x / $distDiff, $diffV3->z / $distDiff));
+ }
+ if($y == 0){
+ $this->pitch = 0;
+ } else {
+ $this->pitch = rad2deg(-atan2($diffV3->y, sqrt($diffV3->x ** 2 + $distDiff->z ** 2)));;
+ }
+ $currentB = $this->getLevel()->getBlock($this->asVector3());
+ if($currentB instanceof \pocketmine\block\Liquid){ // in water, we need to get it floating
+ $this->motionY = $this->gravity * 2;
+ } else {
+ // Check if the ghost is in air and not stuck in the ground. Then, we'll get the target block to check if it's possible to jump.
+ if($currentB->canPassThought()){
+ $targetB = $this->getTargetBlock(2);
+ } else {
+ $targetB = $currentB;
+ }
+ $canJump = true;
+ // Check 3 blocks up that position (to see if the entity can go up)
+ for($i = 1; $i <= 3; $i++){
+ $blockUp = $targetB->asVector3();
+ $blockUp->y += $i;
+ if(!$this->getLevel()->getBlock($blockUp)->canPassThought()) $canJump = false;
+ }
+ // FInally, jump if possible
+ if($canJump && $this->gravity * 3.2 > $this->motionY) $this->motionY = $this->gravity * 3.2;
}
- $this->pitch = $y == 0 ? 0 : rad2deg(-atan2($y, sqrt($x * $x + $z * $z)));
}
/**
@@ -284,9 +333,18 @@ class Ghost extends Human {
* @return void
*/
public function attackEntity(Entity $et){
- $ev = new EntityDamageByEntityEvent($this, $this->associatedPlayer, EntityDamageEvent::CAUSE_ENTITY_ATTACK
- /* Todo Calcule armor */);
- $et->attack($ev);
+ if($et instanceof Player){
+ $damage = [
+ EntityDamageEvent::MODIFIER_BASE => 10 // Two hit a player which has no armor
+ ];
+ $points = 0;
+ foreach($et->getInventory()->getArmorContents() as $armorItem){
+ $points += $armorItem->getDefensePoints();
+ }
+ $damage[EntityDamageEvent::MODIFIER_ARMOR] = -($damage[EntityDamageEvent::MODIFIER_BASE] * $points * 0.04);
+ $ev = new EntityDamageByEntityEvent($this, $this->associatedPlayer, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage);
+ $et->attack($ev);
+ }
}
/**
@@ -295,7 +353,13 @@ class Ghost extends Human {
* @return array
*/
public function getDrops() : array{
- return
+ $it = Item::get(Item::GOLDEN_HOE, 0);
+ $it->setCustomName("§r§cSoul Stealer");
+ $it->setNamedTagEntry(new StringTag("customDamage", 10));
+ $e = Enchantment::getEnchantment(Enchantment::SHARPNESS);
+ $e->setLevel(10);
+ $it->addEnchantment($e);
+ return [$it];
}
diff --git a/src/Ad5001/Spooky/tasks/TickTask.php b/src/Ad5001/Spooky/tasks/TickTask.php
index 19cf705..79a286b 100644
--- a/src/Ad5001/Spooky/tasks/TickTask.php
+++ b/src/Ad5001/Spooky/tasks/TickTask.php
@@ -33,6 +33,87 @@ class TickTask extends PluginTask {
public function onRun(int $tick) {
foreach(self::$ghosts as $i => $g){
self::$ghosts[$i]->currentSec += 0.1;
+ switch(self::$ghosts[$i]->currentSec){
+ case 48: // 0m48s
+ self::$ghosts[$i]->getPlayer()->getLevel()->setTime(16000); // Set time to night
+ break;
+ case 64: // 1m04s
+ self::$ghosts[$i]->blackOutEnterPhase();
+ break;
+ case 66: // 1m06s
+ self::$ghosts[$i]->blackOutExitPhase();
+ self::$ghosts[$i]->intenseFight();
+ break;
+ case 82: // 1m22s
+ self::$ghosts[$i]->movePlayerRandomly();
+ self::$ghosts[$i]->repeatFunc = "move";
+ break;
+ case 88: // 1m28s
+ self::$ghosts[$i]->calmFight();
+ self::$ghosts[$i]->repeatFunc = null;
+ break;
+ case 95: // 1m35s
+ self::$ghosts[$i]->destroyBlocksRandomly();
+ self::$ghosts[$i]->repeatFunc = "blockdis";
+ break;
+ case 100: // 1m40s
+ self::$ghosts[$i]->repeatFunc = null;
+ self::$ghosts[$i]->blackOutEnterPhase();
+ break;
+ case 103: // 1m43s
+ self::$ghosts[$i]->blackOutExitPhase();
+ self::$ghosts[$i]->intenseFight();
+ break;
+ case 136: // 2m16s
+ self::$ghosts[$i]->calmFight();
+ break;
+ case 151: // 2m31s
+ self::$ghosts[$i]->fightType = 0;
+ break;
+ case 152: // 2m32s
+ self::$ghosts[$i]->blackOutEnterPhase();
+ break;
+ case 153: // 2m33s
+ self::$ghosts[$i]->blackOutExitPhase();
+ self::$ghosts[$i]->intenseFight();
+ break;
+ case 168: // 2m48s
+ self::$ghosts[$i]->movePlayerRandomly();
+ self::$ghosts[$i]->repeatFunc = "move";
+ break;
+ case 176: // 2m56s
+ self::$ghosts[$i]->destroyBlocksRandomly();
+ self::$ghosts[$i]->repeatFunc = "blockdis";
+ break;
+ case 183: // 3m03s
+ self::$ghosts[$i]->repeatFunc = null;
+ self::$ghosts[$i]->intenseFight();
+ break;
+ case 197: // 3m17s
+ self::$ghosts[$i]->blackOutEnterPhase();
+ break;
+ case 198: // 3m18s
+ self::$ghosts[$i]->blackOutExitPhase();
+ self::$ghosts[$i]->intenseFight();
+ break;
+ case 227: // 3m47s
+ self::$ghosts[$i]->calmFight();
+ break;
+ case 262: // 4m22
+ self::unregisterGhost(self::$ghosts[$i]);
+ self::$ghosts[$i]->close();
+ break;
+ default:
+ switch(self::$ghosts[$i]->repeatFunc){
+ case "move":
+ self::$ghosts[$i]->movePlayerRandomly();
+ break;
+ case "blockdis":
+ self::$ghosts[$i]->destroyBlocksRandomly();
+ break;
+ }
+ break;
+ }
}
}
@@ -44,6 +125,7 @@ class TickTask extends PluginTask {
*/
public static function registerGhost(Ghost $g){
$g->currentSec = 0;
+ $g->repeatFunc = null;
self::$ghosts[$g->getId()] = $g;
}
diff --git a/timing b/timing
index 5a670d6..630fc34 100644
--- a/timing
+++ b/timing
@@ -17,10 +17,9 @@ Phase 7: 3:47 -> 4:22 (35 secs, more calm period)
If killed before the end, gets reward, otherwise, nothing happends.
-Reward: Hat of despair:
-Pumpkin:
+Reward: Soul Stealer:
+Gold hoe (weapon):
- Night vision,
- when sneak, diseapears (!== invisibility, he cannot be seen nor hit),
-- if he sneaks more than 12 seconds, having nausea
-- If he strikes someone while being invisible, he'll loose invisility
+- if he sneaks more than 12 seconds, having nausea (high so that the screen shakes)
- if he strikes someone otherwise, the person will get a big creepy sound