👻
This commit is contained in:
commit
5dd7716e88
10 changed files with 508 additions and 0 deletions
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Spooky
|
||||
Halloween plugin.
|
||||
|
||||
|
||||
Music used here: NIVIRO - The Return
|
||||
Track's link: https://soundcloud.com/djniviro/thereturn
|
||||
Niviro's website: https://www.djniviro.com
|
3
config.yml
Normal file
3
config.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
# This is the default config generated with ImagicalPlugCreator. (C) ImagicalPlugCreator - Ad5001 2016
|
||||
...
|
9
plugin.yml
Normal file
9
plugin.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: Spooky
|
||||
author: Ad5001
|
||||
version: 1.0
|
||||
api: [3.0.0-ALPHA9]
|
||||
main: Ad5001\Spooky\Main
|
||||
commands: []
|
||||
permissions: []
|
||||
...
|
3
resources/config.yml
Normal file
3
resources/config.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
# This is the default config generated with ImagicalPlugCreator. (C) ImagicalPlugCreator - Ad5001 2016
|
||||
...
|
BIN
resources/ghost_player_data.dat
Normal file
BIN
resources/ghost_player_data.dat
Normal file
Binary file not shown.
82
src/Ad5001/Spooky/Main.php
Normal file
82
src/Ad5001/Spooky/Main.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
namespace Ad5001\Spooky;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\plugin\PluginBase;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\NBT;
|
||||
|
||||
|
||||
use Ad5001\Spooky\entity\Ghost;
|
||||
use Ad5001\Spooky\tasks\TickTask;
|
||||
|
||||
|
||||
class Main extends PluginBase{
|
||||
|
||||
public $ghosts = [];
|
||||
|
||||
/**
|
||||
* When the plugin enables
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onEnable(){
|
||||
// Registering some enchants
|
||||
Enchantement::registerEnchantment(new Enchantement(Enchantement::SHARPNESS, "%enchantment.attack.sharpness", Enchantement::RARITY_COMMON, Enchantement::SLOT_SWORD));
|
||||
// $this->getServer()->getScheduler()->scheduler<Delayed or Repeating>Task(new Task1($this), <TIME>);
|
||||
}
|
||||
|
||||
/**
|
||||
* WHen a command is executed
|
||||
*
|
||||
* @param CommandSender $sender
|
||||
* @param Command $cmd
|
||||
* @param string $label
|
||||
* @param array $args
|
||||
* @return bool
|
||||
*/
|
||||
public function onCommand(CommandSender $sender, Command $cmd, string $label, array $args): bool{
|
||||
switch($cmd->getName()){
|
||||
case "default":
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function spawnGhost(Player $p){
|
||||
// Getting the skin
|
||||
$nbtSkin = new NBT(NBT::BIG_ENDIAN);
|
||||
$nbtSkin->readCompressed(fread($this->getResource("ghost_player_data.dat")));
|
||||
$nbt = new CompoundTag();
|
||||
$nbt->Pos = new ListTag("Pos", [
|
||||
new DoubleTag("", $player->getX()),
|
||||
new DoubleTag("", $player->getY()),
|
||||
new DoubleTag("", $player->getZ())
|
||||
]);
|
||||
$nbt->Motion = new ListTag("Motion", [
|
||||
new DoubleTag("", 0),
|
||||
new DoubleTag("", 0),
|
||||
new DoubleTag("", 0)
|
||||
]);
|
||||
$nbt->Rotation = new ListTag("Rotation", [
|
||||
new FloatTag("", $player->getYaw()),
|
||||
new FloatTag("", $player->getPitch())
|
||||
]);
|
||||
$nbt->Health = new ShortTag("Health", 20);
|
||||
$player->saveNBT();
|
||||
$nbt->Skin = clone $nbtSkin->Skin;
|
||||
$nbt->Inventory = clone $nbtSkin->Inventory;
|
||||
$g = new Ghost($p->getLevel(), $nbt);
|
||||
$g->startFight($p);
|
||||
$this->ghosts[$p->getName()] = $g;
|
||||
}
|
||||
}
|
302
src/Ad5001/Spooky/entity/Ghost.php
Normal file
302
src/Ad5001/Spooky/entity/Ghost.php
Normal file
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
namespace Ad5001\Spooky\entity;
|
||||
|
||||
use pocketmine\Player;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\entity\Human;
|
||||
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\DoubleTag;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\entity\Effect;
|
||||
|
||||
use Ad5001\Spooky\Main;
|
||||
use Ad5001\Spooky\sounds\TheReturnMusicPlay;
|
||||
use Ad5001\Spooky\tasks\TickTask;
|
||||
|
||||
|
||||
|
||||
class Ghost extends Human {
|
||||
|
||||
/**
|
||||
* 0: Not fighting
|
||||
* 1: Simple fight
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $fightType = 0;
|
||||
|
||||
protected $associatedPlayer = null;
|
||||
|
||||
/**
|
||||
* Constructs the class
|
||||
*
|
||||
* @param Level $level
|
||||
* @param CompoundTag $nbt
|
||||
*/
|
||||
public function __construct(Level $level, CompoundTag $nbt) {
|
||||
$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));
|
||||
$this->getInventory()->setItemInHand($it);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the targeted player
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getPlayer(){
|
||||
return $this->associatedPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the speed of the entity
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSpeed(): float {
|
||||
return $this->fightType * 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Phases
|
||||
|
||||
|
||||
/**
|
||||
* Starts the fight.
|
||||
*
|
||||
* @param Player $player
|
||||
*/
|
||||
public function startFight(Player $p) {
|
||||
$p->getLevel()->addSound(new TheReturnMusicPlay($p->asVector3()), [$p->getViewers()]);
|
||||
// Only after the ghost appeared, we can remove it.
|
||||
Effect::getEffectById(Effect::BLINDNESS)->setDuration(66*20)->setVisible(false)->add($p);
|
||||
Effect::getEffectById(Effect::INVISIBILITY)->setDuration(63*20)->setVisible(false)->add($this);
|
||||
$this->spawnTo($p);
|
||||
$this->associatedPlayer = $p;
|
||||
TickTask::registerGhost($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a sequence where the ghost is in an intense fight.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function intenseFight(){
|
||||
if(!$this->checkIfConnected()) return;
|
||||
// TODO: Custom intense fight
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a sequence where the ghost is in an calm fight.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function calmFight(){
|
||||
if(!$this->checkIfConnected()) return;
|
||||
$this->fightType = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a sequence where the ghost blinds the player,
|
||||
* slows him down (zooming effect w/ fov) and he appears invuulnerable
|
||||
* in front of the player.
|
||||
* @return void
|
||||
*/
|
||||
public function blackOutEnterPhase(){
|
||||
if(!$this->checkIfConnected()) return;
|
||||
$this->fightType = 0;
|
||||
$spawnBlock = $this->getPlayer()->getLineOfSight(2, 3, []);
|
||||
$spawnBlock = $spawnBlock[count($spawnBlock) -1];
|
||||
$this->getPlayer()->addEffect(
|
||||
Effect::getEffectById(Effect::BLINDNESS)->setDuration(30*20)->setAmplifier(4)->setVisible(false)
|
||||
);
|
||||
$this->getPlayer()->addEffect(
|
||||
Effect::getEffectById(Effect::SLOWNESS)->setDuration(30*20)->setAmplifier(99)->setVisible(false)
|
||||
);
|
||||
$this->getPlayer()->addEffect(
|
||||
Effect::getEffectById(Effect::NAUSEA)->setDuration(30*20)->setAmplifier(99)->setVisible(false)
|
||||
);
|
||||
$this->teleport(new Vector3($spawnBlock->x, $spawnBlock->y, $spawnBlock->z), abs($this->getPlayer()->getYaw() - 180));
|
||||
$this->removeEffect(Effect::INVISIBILITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a sequence where the ghost blinds the player,
|
||||
* slows him down (zooming effect w/ fov) and he appears invuulnerable
|
||||
* in front of the player.
|
||||
* @return void
|
||||
*/
|
||||
public function blackOutExitPhase(){
|
||||
if(!$this->checkIfConnected()) return;
|
||||
$this->getPlayer()->removeEffect(Effect::BLINDNESS);
|
||||
$this->getPlayer()->removeEffect(Effect::NAUSEA);
|
||||
$this->getPlayer()->removeEffect(Effect::SLOWNESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence where the ghost starts
|
||||
* to move the player randomly.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function movePlayerRandomly(){
|
||||
if(!$this->checkIfConnected()) return;
|
||||
$this->fightType = 0;
|
||||
for($i = 0; $i < 10; $i++){
|
||||
$this->associatedPlayer->setMotion(new Vector3(rand() - rand(), 0, rand() - rand()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence where the ghost starts
|
||||
* to destroy blocks around the player randomly.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroyBlocksRandomly(){
|
||||
if(!$this->checkIfConnected()) return;
|
||||
$this->fightType = 0;
|
||||
$this->teleport(new Vector3($this->getPlayer()->x, $this->getPlayer()->y + 7, $this->getPlayer()->z), $this->yaw == 0 ? 359 : $this->yaw - 1, 130);
|
||||
srand(hash("sha512", time()));
|
||||
foreach($this->getLevel()->getEntities() as $et){
|
||||
if($et instanceof \pocketmine\entity\FallingSand && $et->namedtag->getInt("SpawnTime") !== $null){
|
||||
$diffTime = time() - $et->namedtag->getInt("SpawnTime");
|
||||
if($diffTime < 4) $et->close();
|
||||
}
|
||||
}
|
||||
for($i = 0; $i < 3; $i++) {
|
||||
$rx = rand(0, 12) - 6;
|
||||
$rz = rand(0, 12) - 6;
|
||||
for($ry = 128; $this->getLevel()->getBlock($rx, $ry, $rz)->getId() !== 0 || $ry == 0; $ry--){} // Determining y from the higthest workable block
|
||||
$b = $this->getLevel()->getBlock($rx, $ry, $rz);
|
||||
// Creating falling sand block
|
||||
$nbt = Entity::createBaseNBT($b->asVector3()->add(0.5, 0, 0.5));
|
||||
$nbt->setInt("TileID", $b->getId());
|
||||
$nbt->setInt("SpawnTime", time());
|
||||
$nbt->setByte("Data", $b->getDamage());
|
||||
$nbt->getListTag("Motion")[1] = 3;
|
||||
$fall = Entity::createEntity("FallingSand", $this->getLevel(), $nbt);
|
||||
if($fall !== null){
|
||||
$fall->spawnToAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the player is still connected to the server
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkIfConnected(){
|
||||
if(!$this->associatedPlayer->isOnline()){
|
||||
TickTask::unregisterGhost($this);
|
||||
$this->close();
|
||||
return false;
|
||||
}
|
||||
return !($this->isClosed() || $this->getLevel() == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns the ghost to the player
|
||||
*
|
||||
* @param Player $player
|
||||
* @return void
|
||||
*/
|
||||
public function spawnTo(Player $player) {
|
||||
if(!isset($this->hasSpawned[$player->getLoaderId()]) &&
|
||||
isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])) {
|
||||
$this->hasSpawned[$player->getLoaderId()] = $player;
|
||||
$uuid = $this->getUniqueId();
|
||||
$entityId = $this->getId();
|
||||
$pk = new AddPlayerPacket();
|
||||
$pk->uuid = $uuid;
|
||||
$pk->username = "";
|
||||
$pk->entityRuntimeId = $entityId;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->item = $this->getInventory()->getItemInHand();
|
||||
$pk->metadata = $this->dataProperties;
|
||||
$pk->metadata[self::DATA_NAMETAG] = [self::DATA_TYPE_STRING, $this->getDisplayName($player)];
|
||||
$player->dataPacket($pk);
|
||||
$this->inventory->sendArmorContents($player);
|
||||
$player->getServer()->updatePlayerListData($uuid, $entityId, "The Ghost", $this->skinId, $this->skin, [$player]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Event listeners
|
||||
/**
|
||||
* Check the damage to reduce it by 25%
|
||||
*
|
||||
* @param EntityDamageEvent $source
|
||||
*/
|
||||
public function attack(EntityDamageEvent $source) {
|
||||
if($event instanceof EntityDamageByEntityEvent) {
|
||||
if($event->getDamager() instanceof Player && $event->getDamager()){
|
||||
$source->setDamage($source->getDamage() * 0.75);
|
||||
} else {
|
||||
$event->setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the entity gets an update, recalibrate entity
|
||||
*
|
||||
* @param int $currentTick
|
||||
* @return bool
|
||||
*/
|
||||
public function onUpdate(int $currentTick): bool {
|
||||
if(!$this->checkIfConnected()) return;
|
||||
$diffV3 = $this->associatedPlayer->asVector3()->subtract($this->asVector3());
|
||||
$distDiff = $diffV3->asVector3()->abs();
|
||||
$distDiff = $distDiff->x + $distDiff->z;
|
||||
if($this->distanceSquared($this->associatedPlayer) <= 1){
|
||||
$this->attackEntity($this->associatedPlayer);
|
||||
}
|
||||
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->pitch = $y == 0 ? 0 : rad2deg(-atan2($y, sqrt($x * $x + $z * $z)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks an entity when the ghost is close enought from it.
|
||||
*
|
||||
* @param Entity $et
|
||||
* @return void
|
||||
*/
|
||||
public function attackEntity(Entity $et){
|
||||
$ev = new EntityDamageByEntityEvent($this, $this->associatedPlayer, EntityDamageEvent::CAUSE_ENTITY_ATTACK
|
||||
/* Todo Calcule armor */);
|
||||
$et->attack($ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives some drops when the ghost die
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDrops() : array{
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
15
src/Ad5001/Spooky/sounds/TheReturnMusicPlay.php
Normal file
15
src/Ad5001/Spooky/sounds/TheReturnMusicPlay.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Ad5001\Spooky\sounds;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\level\sound\GenericSound;
|
||||
|
||||
|
||||
class TheReturnMusicPlay extends GenericSound{
|
||||
|
||||
public function __construct(Vector3 $pos, $pitch = 1){
|
||||
parent::__construct($pos, LevelEventPacket::EVENT_SOUND_ARMOR_STAND_HIT, $pitch);
|
||||
}
|
||||
}
|
61
src/Ad5001/Spooky/tasks/TickTask.php
Normal file
61
src/Ad5001/Spooky/tasks/TickTask.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Ad5001\Spooky\tasks;
|
||||
|
||||
use pocketmine\Server;
|
||||
use pocketmine\scheduler\PluginTask;
|
||||
use pocketmine\Player;
|
||||
|
||||
use Ad5001\Spooky\Main;
|
||||
use Ad5001\Spooky\entity\Ghost;
|
||||
|
||||
|
||||
|
||||
class TickTask extends PluginTask {
|
||||
protected static $ghosts = [];
|
||||
|
||||
/**
|
||||
* Constructs the class
|
||||
*
|
||||
* @param Main $main
|
||||
*/
|
||||
public function __construct(Main $main) {
|
||||
parent::__construct($main);
|
||||
$this->main = $main;
|
||||
$this->server = $main->getServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick tack...
|
||||
*
|
||||
* @param int $tick
|
||||
* @return void
|
||||
*/
|
||||
public function onRun(int $tick) {
|
||||
foreach(self::$ghosts as $i => $g){
|
||||
self::$ghosts[$i]->currentSec += 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a ghost to the class
|
||||
*
|
||||
* @param Ghost $g
|
||||
* @return void
|
||||
*/
|
||||
public static function registerGhost(Ghost $g){
|
||||
$g->currentSec = 0;
|
||||
self::$ghosts[$g->getId()] = $g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a ghost from the class
|
||||
*
|
||||
* @param Ghost $g
|
||||
* @return void
|
||||
*/
|
||||
public static function unregisterGhost(Ghost $g){
|
||||
unset(self::$ghosts[$g->getId()]);
|
||||
}
|
||||
|
||||
|
||||
}
|
26
timing
Normal file
26
timing
Normal file
|
@ -0,0 +1,26 @@
|
|||
Blindness start: 0:00
|
||||
Ghost appears + Slowness: 1:04 -> 1:05
|
||||
Phase 1 stop blindness: 1:06 -> 1:22 (16 secs, short & intense fight period)
|
||||
Moving player around randomly: 1:22 -> 1:28
|
||||
Blocks diseapear around it: 1:35 -> 1:40
|
||||
Reblindess + Slowness + Ghost appear: 1:40 -> 1:43
|
||||
Phase 2: 1:43 -> 2:16 (31 secs, intense more long fight period)
|
||||
Phase 3: 2:17 -> 2:31 (14 secs more calm period)
|
||||
Reblindess + Slowness + Ghost appear: 2:32 -> 2:33
|
||||
Phase 4: 2:34 -> 2:48 (12 secs, intense period)
|
||||
Moving player around randomly: 2:49 -> 2:56
|
||||
Blocks diseapear around it: 2:56 -> 3:03
|
||||
Phase 5: 3:04 -> 3:17 (13 secs, get higher period)
|
||||
Reblindess + Slowness + Ghost appear: 3:17 -> 3:18
|
||||
Phase 6: 3:19 -> 3:47 (28 secs, intense period)
|
||||
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:
|
||||
- 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 strikes someone otherwise, the person will get a big creepy sound
|
Loading…
Reference in a new issue