Files

218 lines
7.8 KiB
Lua

ConversationState = {
Disabled = 0,
Talking = 1,
Waiting = 2,
}
function Conversation()
local self = {
state = ConversationState.Disabled,
percent = 0,
character = nil,
font = SpriteFont(),
advance_font = SpriteFont(""),
choice_fonts = {},
font_blink_timer = 0,
dialog = {},
speed = 30,
choice = 1,
override_input = false,
Update = function(self, path, scene, player)
local crop_top = GetScreenHeight() * 0.17
local crop_bottom = GetScreenHeight() * 0.21
local text_offset = GetScreenWidth() * 0.2
self.font.SetPos(Vector(text_offset, GetScreenHeight() - crop_bottom + 10))
self.font.SetHorizontalWrapping(GetScreenWidth() - self.font.GetPos().GetX() * 2)
self.advance_font.SetPos(Vector(GetScreenWidth() - self.font.GetPos().GetX(), GetScreenHeight() - 50))
-- Update conversation percentage (fade in/out of conversation)
if self.state == ConversationState.Disabled then
self.percent = math.lerp(self.percent, 0, getDeltaTime() * 4)
else
self.percent = math.lerp(self.percent, 1, getDeltaTime() * 4)
end
path.SetCropTop(self.percent * crop_top)
path.SetCropBottom(self.percent * crop_bottom)
local cam = GetCamera()
cam.SetApertureSize(self.percent)
if self.percent < 0.9 then
self.font.SetHidden(true)
self.font.ResetTypewriter()
else
self.font.SetHidden(false)
end
self.advance_font.SetColor(Vector())
self.override_input = false
for i,choice_font in ipairs(self.choice_fonts) do
choice_font.SetColor(Vector())
end
self.font_blink_timer = self.font_blink_timer + getDeltaTime()
-- Focus on character:
if self.character ~= nil then
cam.SetFocalLength(vector.Subtract(scene.Component_GetTransform(self.character.head).GetPosition(), cam.GetPosition()).Length())
end
-- State flow:
if self.state == ConversationState.Disabled then
self.font.SetHidden(true)
elseif self.state == ConversationState.Talking then
self.choice = 1
self.override_input = true
self:CinematicCamera(self.character, player, scene, cam)
-- End of talking:
if self.font.IsTypewriterFinished() then
self.state = ConversationState.Waiting
self.font_blink_timer = 0
end
-- Skip talking:
if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_2) then
self.font.TypewriterFinish()
end
-- Turn on talking animation:
if self.character.expression ~= INVALID_ENTITY and self.percent >= 0.9 then
scene.Component_GetExpression(self.character.expression).SetForceTalkingEnabled(true)
end
elseif self.state == ConversationState.Waiting then
-- End of talking, waiting for input:
if self.dialog.choices ~= nil then
-- Dialog choices:
self.override_input = true
self:CinematicCamera(player, self.character, scene, cam)
local pos = vector.Add(self.font.GetPos(), Vector(20, 10 + self.font.TextSize().GetY()))
for i,choice in ipairs(self.dialog.choices) do
if self.choice_fonts[i] == nil then
self.choice_fonts[i] = SpriteFont()
path.AddFont(self.choice_fonts[i])
end
self.choice_fonts[i].SetPos(pos)
self.choice_fonts[i].SetSize(self.font.GetSize())
self.choice_fonts[i].SetHorizontalWrapping(GetScreenWidth() - self.choice_fonts[i].GetPos().GetX() * 2)
if i == self.choice then
self.choice_fonts[i].SetText("" .. choice[1])
self.choice_fonts[i].SetColor(vector.Lerp(Vector(1,1,1,1), self.font.GetColor(), math.abs(math.sin(self.font_blink_timer * math.pi))))
else
self.choice_fonts[i].SetText(" " .. choice[1])
self.choice_fonts[i].SetColor(self.font.GetColor())
end
pos = vector.Add(pos, Vector(0, self.choice_fonts[i].TextSize().GetY() + 5))
end
-- Dialog input:
if input.Press(KEYBOARD_BUTTON_UP) or input.Press(string.byte('W')) or input.Press(GAMEPAD_BUTTON_UP) or input.Press(GAMEPAD_ANALOG_THUMBSTICK_L_AS_BUTTON_UP) then
self.choice = self.choice - 1
self.font_blink_timer = 0
end
if input.Press(KEYBOARD_BUTTON_DOWN) or input.Press(string.byte('S')) or input.Press(GAMEPAD_BUTTON_DOWN) or input.Press(GAMEPAD_ANALOG_THUMBSTICK_L_AS_BUTTON_DOWN) then
self.choice = self.choice + 1
self.font_blink_timer = 0
end
if self.choice < 1 then
self.choice = #self.dialog.choices
end
if self.choice > #self.dialog.choices then
self.choice = 1
end
if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_2) then
if self.dialog.choices[self.choice].action ~= nil then
self.dialog.choices[self.choice].action() -- execute dialog choice action
end
self:Next()
end
else
-- No dialog choices:
self.override_input = true
self:CinematicCamera(self.character, player, scene, cam)
if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_2) then
if self.dialog.action_after ~= nil then
self.dialog.action_after() -- execute dialog action after ended
end
self:Next()
self.font.ResetTypewriter()
end
-- Blinking advance conversation icon:
self.advance_font.SetColor(vector.Lerp(Vector(0,0,0,0), self.font.GetColor(), math.abs(math.sin(self.font_blink_timer * math.pi))))
end
-- Turn off talking animation:
if self.character.expression ~= INVALID_ENTITY then
scene.Component_GetExpression(self.character.expression).SetForceTalkingEnabled(false)
end
end
end,
Enter = function(self, character)
backlog_post("Entering conversation")
self.state = ConversationState.Talking
self.character = character
if #self.character.dialogs < self.character.next_dialog then
self.character.next_dialog = 1
end
self:Next()
end,
Exit = function(self)
backlog_post("Exiting conversation")
self.state = ConversationState.Disabled
self.dialog = {}
self.override_input = false
self.font.ResetTypewriter()
end,
Next = function(self)
if #self.character.dialogs < self.character.next_dialog then
self:Exit()
end
if self.state == ConversationState.Disabled then
return
end
self.dialog = self.character.dialogs[self.character.next_dialog]
self.character.next_dialog = self.character.next_dialog + 1
self.state = ConversationState.Talking
self.font.SetText(self.dialog[1])
self.font.SetTypewriterTime(string.len(self.dialog[1] or "") / self.speed)
self.font.ResetTypewriter()
if self.dialog.action ~= nil then
self.dialog.action() -- execute dialog action
end
end,
CinematicCamera = function(self, character_foreground, character_background, scene, camera)
local head_pos = scene.Component_GetTransform(character_foreground.head).GetPosition()
local forward_dir = vector.Subtract(character_background.position, character_foreground.position).Normalize()
forward_dir = vector.TransformNormal(forward_dir, matrix.RotationY(math.pi * 0.1))
local up_dir = Vector(0,1,0)
local right_dir = vector.Cross(up_dir, forward_dir).Normalize()
local cam_pos = vector.Add(head_pos, forward_dir)
local cam_target = vector.Add(vector.Add(head_pos, vector.Multiply(right_dir, -0.4)), Vector(0,-0.1))
local lookat = matrix.Inverse(matrix.LookAt(cam_pos, cam_target))
camera.TransformCamera(lookat)
camera.UpdateCamera()
camera.SetFocalLength(vector.Subtract(head_pos, camera.GetPosition()).Length())
end
}
self.font.SetSize(20)
self.font.SetColor(Vector(0.6,0.8,1,1))
self.advance_font.SetSize(24)
local sound = Sound()
local soundinstance = SoundInstance()
audio.CreateSound(script_dir() .. "typewriter.wav", sound)
audio.CreateSoundInstance(sound, soundinstance)
audio.SetVolume(0.2, soundinstance)
self.font.SetTypewriterSound(sound, soundinstance)
return self
end