From 7fa69be4e88f1fcf057871fec7e4503f50578465 Mon Sep 17 00:00:00 2001
From: Kelly Rauchenberger <fefferburbia@gmail.com>
Date: Mon, 1 Mar 2021 22:19:46 -0500
Subject: Started writing the Mixolydia scene!

Looking pretty good so far.

TODO: direction facing functions have inverted Y coordinate. confusion expression doesn't exist yet. rest of scene.
---
 res/maps/pink_shell.tmx                  |  17 +++-
 res/scripts/common.lua                   |  59 ++++++++++++
 res/scripts/hallucination_hot_spring.lua |   2 +
 res/scripts/hallucination_interior.lua   |   3 +
 res/scripts/pink_shell.lua               | 148 +++++++++++++++++++++++++++++++
 src/behaviour_system.cpp                 |   1 +
 src/character_system.cpp                 |   1 +
 src/direction.h                          |  17 ++++
 src/main.cpp                             |   4 +-
 src/script_system.cpp                    |   7 +-
 10 files changed, 255 insertions(+), 4 deletions(-)

diff --git a/res/maps/pink_shell.tmx b/res/maps/pink_shell.tmx
index d7915d9..2389b70 100644
--- a/res/maps/pink_shell.tmx
+++ b/res/maps/pink_shell.tmx
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<map version="1.4" tiledversion="1.4.3" orientation="orthogonal" renderorder="right-down" width="32" height="32" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="5">
+<map version="1.4" tiledversion="1.4.3" orientation="orthogonal" renderorder="right-down" width="32" height="32" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="11">
  <properties>
   <property name="music" value="are_you_gorgeous"/>
  </properties>
@@ -74,6 +74,21 @@
    </properties>
    <point/>
   </object>
+  <object id="5" name="lucas_lineup" type="warp" x="248" y="144">
+   <point/>
+  </object>
+  <object id="7" name="boney_lineup" type="warp" x="232" y="144">
+   <point/>
+  </object>
+  <object id="8" name="kumatora_lineup" type="warp" x="264" y="144">
+   <point/>
+  </object>
+  <object id="9" name="duster_lineup" type="warp" x="280" y="144">
+   <point/>
+  </object>
+  <object id="10" name="claus_lineup" type="warp" x="296" y="144">
+   <point/>
+  </object>
  </objectgroup>
  <layer id="1" name="Upper" width="32" height="32">
   <properties>
diff --git a/res/scripts/common.lua b/res/scripts/common.lua
index 1b0c4a9..dad9c5d 100644
--- a/res/scripts/common.lua
+++ b/res/scripts/common.lua
@@ -30,6 +30,12 @@ SpriteLayer = {
   ABOVE = 1
 }
 
+BehaviourType = {
+  NONE = 0,
+  WANDER = 1,
+  PATH = 2
+}
+
 CutsceneOptions = {
   DO_NOT_CHANGE_ANIMATION = 1 -- Prevents player party animation being set to "frozen" at the start of a cutscene or "still" at the end
 }
@@ -426,6 +432,13 @@ function WaitForSpritePath(spriteName)
   end
 end
 
+--- Turns off the sprite's behaviour.
+function DisableBehaviour(spriteName)
+  local spriteId = getSpriteByAlias(spriteName)
+  local sprite = getSprite(spriteId)
+  sprite.behaviourType = BehaviourType.NONE
+end
+
 --- Fades out the currently playing music.
 -- This does not block. If you want it to block, call Delay for the same amount
 -- of time.
@@ -456,3 +469,49 @@ function EnablePlayerControl()
   local playerSprite = getSprite(playerId)
   playerSprite.controllable = true
 end
+
+--- Makes the specified sprite face toward the †arget sprite.
+-- This version of the function uses the closest cardinal direction.
+-- @param spriteName the name of the sprite to change the direction of
+-- @param targetName the name of the sprite to face toward
+function FaceTowardSpriteCardinally(spriteName, targetName)
+  local spriteId = getSpriteByAlias(spriteName)
+  local targetId = getSpriteByAlias(targetName)
+  local sprite = getSprite(spriteId)
+  local target = getSprite(targetId)
+  local diff = vec2i.new(target.loc:x() - sprite.loc:x(), target.loc:y() - sprite.loc:y())
+  local dir = cardinalDirectionFacingPoint(diff)
+
+  SetDirection(spriteName, dir)
+end
+
+--- Detaches the sprite's followers and erases their following trails.
+function BreakUpParty(spriteName)
+  local spriteId = getSpriteByAlias(spriteName)
+  character():breakUpParty(spriteId)
+end
+
+--- Makes the specified sprite solid.
+-- This means that other sprites will be blocked if they collide with this one.
+function MakeSpriteSolid(spriteName)
+  local spriteId = getSpriteByAlias(spriteName)
+  local sprite = getSprite(spriteId)
+  sprite.solid = true
+end
+
+--- Makes the specified sprite not solid.
+-- This means that other sprites will not be blocked if they collide with this
+-- one.
+function MakeSpriteNotSolid(spriteName)
+  local spriteId = getSpriteByAlias(spriteName)
+  local sprite = getSprite(spriteId)
+  sprite.solid = false
+end
+
+--- Sets the sprite's movement speed.
+-- As a reference: 1 is slow (good for NPCs), 2 is Lucas's default walking speed
+function SetMovementSpeed(spriteName, speed)
+  local spriteId = getSpriteByAlias(spriteName)
+  local sprite = getSprite(spriteId)
+  sprite.movementSpeed = speed
+end
diff --git a/res/scripts/hallucination_hot_spring.lua b/res/scripts/hallucination_hot_spring.lua
index c432eb3..805f85a 100644
--- a/res/scripts/hallucination_hot_spring.lua
+++ b/res/scripts/hallucination_hot_spring.lua
@@ -14,6 +14,8 @@ function hallucination_hot_spring.off_right()
 end
 
 function hallucination_hot_spring.enter_hot_spring()
+  gamestate.went_in_hot_spring = true
+
   if gamestate.ionia_in_water then
     -- Soft cutscene start; don't show bars but do take away control
     DisablePlayerControl()
diff --git a/res/scripts/hallucination_interior.lua b/res/scripts/hallucination_interior.lua
index 4fbaa99..9ef808e 100644
--- a/res/scripts/hallucination_interior.lua
+++ b/res/scripts/hallucination_interior.lua
@@ -45,6 +45,9 @@ function hallucination_interior.join_claus()
 
     local clausSprite = getSprite(clausId)
     clausSprite.persistent = true
+
+    gamestate.claus_joined = true
+    gamestate.still_has_claus = true
   else
     DisplayMessage("* You won't let me join in?\nWhy not? Why not?\n\f* Why won't you let me join in?", "Claus", SpeakerType.MAN)
     WaitForEndOfMessage()
diff --git a/res/scripts/pink_shell.lua b/res/scripts/pink_shell.lua
index 4223188..5747186 100644
--- a/res/scripts/pink_shell.lua
+++ b/res/scripts/pink_shell.lua
@@ -16,4 +16,152 @@ end
 
 function pink_shell.talk_to_mixolydia()
   SetDirection("mixolydia", Direction.UP)
+  SetAnimation("mixolydia", "talk")
+  StartCutscene()
+  DisplayMessage("* Oh, me, oh, my! We have visitors! `", "Mixolydia", SpeakerType.WOMAN)
+  WaitForEndOfMessage()
+
+  SetAnimation("mixolydia", "still")
+  Delay(500)
+
+  SetDirection("mixolydia", Direction.DOWN)
+  Delay(1000)
+
+  if gamestate.went_in_hot_spring then
+    SetAnimation("mixolydia", "talk")
+    DisplayMessage("* ...You people stink.", "Mixolydia", SpeakerType.WOMAN)
+    WaitForEndOfMessage()
+
+    SetAnimation("mixolydia", "still")
+    Delay(1000)
+  end
+
+  FaceTowardSpriteCardinally("mixolydia", "lucas")
+  ShowExpression("mixolydia", "confusion")
+  Delay(1000)
+
+  RemoveExpression("mixolydia")
+  SetAnimation("mixolydia", "talk")
+  DisplayMessage("* Oh, wait a minute...", "Mixolydia", SpeakerType.WOMAN)
+  WaitForEndOfMessage()
+
+  SetAnimation("mixolydia", "still")
+  Delay(1000)
+
+  SetAnimation("mixolydia", "talk")
+  DisplayMessage("* Are you Lucas?", "Mixolydia", SpeakerType.WOMAN)
+  ShowChoice("Yes", "No")
+  WaitForEndOfMessage()
+
+  if GetChoiceSelection() == 1 then
+    DisplayMessage("* This island really has done a number on you.\n\f* But I'll humor you and listen anyway.", "Mixolydia", SpeakerType.WOMAN)
+    WaitForEndOfMessage()
+  end
+
+  if gamestate.went_in_hot_spring then
+    SetAnimation("mixolydia", "still")
+    Delay(1000)
+
+    SetAnimation("mixolydia", "talk")
+    DisplayMessage("* ...Wow, you guys really stink.", "Mixolydia", SpeakerType.WOMAN)
+    WaitForEndOfMessage()
+  end
+
+  SetAnimation("mixolydia", "still")
+  Delay(2000)
+
+  SetAnimation("mixolydia", "talk")
+  DisplayMessage("* Ionia told me about you. `\n\f* I'm Mixolydia, one of the Magifolk. `\n\f* If that's too hard to remember, <Mixo>...\n... no, <Missy> will do just fine. `", "Mixolydia", SpeakerType.WOMAN)
+  WaitForEndOfMessage()
+
+  SetAnimation("mixolydia", "still")
+  Delay(2000)
+
+  SetAnimation("mixolydia", "talk")
+  DisplayMessage("* Okay... Line up here.", "Mixolydia", SpeakerType.WOMAN)
+  WaitForEndOfMessage()
+
+  SetAnimation("mixolydia", "still")
+  Delay(1000)
+
+  -- direct everyone to stand in their positions
+  BreakUpParty("lucas")
+  MakeSpriteNotSolid("lucas")
+  MakeSpriteNotSolid("mixolydia")
+
+  UnpauseSprite("lucas")
+  SetMovementSpeed("lucas", 1)
+  DirectSpriteToLocation("lucas", "lucas_lineup", PathfindingOptions.CARDINAL_DIRECTIONS_ONLY)
+  WaitForSpritePath("lucas")
+  DisableBehaviour("lucas")
+  SetDirection("lucas", Direction.DOWN)
+  SetAnimation("lucas", "tired")
+  PauseSprite("lucas")
+  SetMovementSpeed("lucas", 2)
+  Delay(100)
+
+  UnpauseSprite("kuma")
+  SetMovementSpeed("kuma", 1)
+  DirectSpriteToLocation("kuma", "kumatora_lineup", PathfindingOptions.CARDINAL_DIRECTIONS_ONLY)
+  WaitForSpritePath("kuma")
+  DisableBehaviour("kuma")
+  SetDirection("kuma", Direction.DOWN)
+  SetAnimation("kuma", "tired")
+  PauseSprite("kuma")
+  SetMovementSpeed("kuma", 0)
+  Delay(100)
+
+  UnpauseSprite("duster")
+  SetMovementSpeed("duster", 1)
+  DirectSpriteToLocation("duster", "duster_lineup", PathfindingOptions.CARDINAL_DIRECTIONS_ONLY)
+  WaitForSpritePath("duster")
+  DisableBehaviour("duster")
+  SetDirection("duster", Direction.DOWN)
+  SetAnimation("duster", "tired")
+  PauseSprite("duster")
+  SetMovementSpeed("duster", 0)
+  Delay(100)
+
+  UnpauseSprite("boney")
+  SetMovementSpeed("boney", 1)
+  DirectSpriteToLocation("boney", "boney_lineup", PathfindingOptions.CARDINAL_DIRECTIONS_ONLY)
+  WaitForSpritePath("boney")
+  DisableBehaviour("boney")
+  SetDirection("boney", Direction.DOWN)
+  SetAnimation("boney", "tired")
+  PauseSprite("boney")
+  SetMovementSpeed("boney", 0)
+  Delay(100)
+
+  if gamestate.still_has_claus then
+    UnpauseSprite("join_claus")
+    SetMovementSpeed("join_claus", 1)
+    DirectSpriteToLocation("join_claus", "claus_lineup", PathfindingOptions.CARDINAL_DIRECTIONS_ONLY)
+    WaitForSpritePath("join_claus")
+    DisableBehaviour("join_claus")
+    SetDirection("join_claus", Direction.DOWN)
+    SetAnimation("join_claus", "tired")
+    PauseSprite("join_claus")
+    SetMovementSpeed("join_claus", 0)
+  end
+
+  MakeSpriteSolid("lucas")
+  Delay(1000)
+
+  if gamestate.went_in_hot_spring then
+    SetAnimation("mixolydia", "talk")
+    DisplayMessage("* ...Yuck. What a stench.", "Mixolydia", SpeakerType.WOMAN)
+    WaitForEndOfMessage()
+
+    SetAnimation("mixolydia", "still")
+    Delay(1000)
+  end
+
+  SetAnimation("mixolydia", "talk")
+  DisplayMessage("* Tanetane Island...\nIt wreaks havoc on a person's mind.\n\f* Every trauma you've suffered is pulled out.\n\f* The things down there tear at your weaknesses and the scars in your heart.\n\f* But I'll bring you back to your senses now. `", "Mixolydia", SpeakerType.WOMAN)
+  WaitForEndOfMessage()
+
+  SetAnimation("mixolydia", "still")
+
+  -- TODO: rest of scene
 end
diff --git a/src/behaviour_system.cpp b/src/behaviour_system.cpp
index 4a194f0..a05912c 100644
--- a/src/behaviour_system.cpp
+++ b/src/behaviour_system.cpp
@@ -59,6 +59,7 @@ void BehaviourSystem::tick(double dt) {
 
 void BehaviourSystem::directSpriteToLocation(int spriteId, vec2i pos, PathfindingOptions options) {
   Sprite& sprite = game_.getSprite(spriteId);
+  sprite.orientable = true;
   sprite.behaviourType = BehaviourType::Path;
   sprite.pathfindingDestination = pos;
   sprite.cardinalDirectionsOnly = pathfindingOptionsContains(options, PathfindingOptions::CardinalDirectionsOnly);
diff --git a/src/character_system.cpp b/src/character_system.cpp
index 53debb2..48d2a33 100644
--- a/src/character_system.cpp
+++ b/src/character_system.cpp
@@ -17,6 +17,7 @@ void CharacterSystem::initSprite(int spriteId, int movementSpeed) {
 void CharacterSystem::addSpriteToParty(int leaderId, int followerId) {
   Sprite& leader = game_.getSprite(leaderId);
   Sprite& follower = game_.getSprite(followerId);
+  follower.orientable = false;
 
   vec2i targetPos = leader.loc;
 
diff --git a/src/direction.h b/src/direction.h
index 3dd95f9..595693f 100644
--- a/src/direction.h
+++ b/src/direction.h
@@ -90,4 +90,21 @@ inline Direction directionFacingPoint(vec2i point) {
   }
 }
 
+inline Direction cardinalDirectionFacingPoint(vec2i point) {
+  double theta = atan2(point.y(), point.x());
+  theta /= M_PI;
+
+  if (theta < -3.0/4.0) {
+    return Direction::left;
+  } else if (theta < -1.0/4.0) {
+    return Direction::down;
+  } else if (theta < 1.0/4.0) {
+    return Direction::right;
+  } else if (theta < 3.0/4.0) {
+    return Direction::up;
+  } else {
+    return Direction::left;
+  }
+}
+
 #endif /* end of include guard: DIRECTION_H_AB66A90E */
diff --git a/src/main.cpp b/src/main.cpp
index b98c8f1..d0220fc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -27,9 +27,9 @@ void loop(Renderer& renderer, std::mt19937& rng) {
   game.emplaceSystem<MessageSystem>();
   game.emplaceSystem<EffectSystem>();
 
-  game.loadMap("hallucination_interior");
+  game.loadMap("pink_shell");
 
-  vec2i warpLoc = game.getMap().getWarpPoint("debugWarp_rightside");
+  vec2i warpLoc = game.getMap().getWarpPoint("fromOutside");
 
   int lucasSprite = game.emplaceSprite("lucas");
   game.getSystem<TransformSystem>().initSprite(lucasSprite, warpLoc);
diff --git a/src/script_system.cpp b/src/script_system.cpp
index 931759d..a3686b4 100644
--- a/src/script_system.cpp
+++ b/src/script_system.cpp
@@ -44,7 +44,10 @@ ScriptSystem::ScriptSystem(Game& game) : game_(game) {
     "cantCrouch", &Sprite::cantCrouch,
     "bobsWhenNormal", &Sprite::bobsWhenNormal,
     "animSlowdown", &Sprite::animSlowdown,
-    "enclosureZone", &Sprite::enclosureZone);
+    "enclosureZone", &Sprite::enclosureZone,
+    "movementSpeed", &Sprite::movementSpeed,
+    "solid", &Sprite::solid,
+    "behaviourType", &Sprite::behaviourType);
 
   engine_.new_usertype<MessageSystem>(
     "message",
@@ -230,6 +233,8 @@ ScriptSystem::ScriptSystem(Game& game) : game_(game) {
       loadMapScripts(filename);
     });
 
+  engine_.set_function("cardinalDirectionFacingPoint", &cardinalDirectionFacingPoint);
+
   engine_.script_file("../res/scripts/common.lua");
 }
 
-- 
cgit 1.4.1