mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-12-24 11:06:51 -05:00
Merge pull request #983 from gucio321/quest-log-part2
Quest Log - quest completion animation
This commit is contained in:
commit
c79c2a8c89
@ -193,6 +193,7 @@ const (
|
||||
QuestLogQDescrBtn = "/data/global/ui/MENU/questlast.dc6"
|
||||
QuestLogSocket = "/data/global/ui/MENU/questsockets.dc6"
|
||||
QuestLogAQuestAnimation = "/data/global/ui/MENU/a%dq%d.dc6"
|
||||
QuestLogDoneSfx = "cursor/questdone.wav"
|
||||
|
||||
// --- Mouse Pointers ---
|
||||
|
||||
|
@ -304,7 +304,8 @@ func (v *Game) bindGameControls() error {
|
||||
|
||||
var err error
|
||||
v.gameControls, err = d2player.NewGameControls(v.asset, v.renderer, player, v.gameClient.MapEngine,
|
||||
v.escapeMenu, v.mapRenderer, v, v.terminal, v.uiManager, v.keyMap, v.logLevel, v.gameClient.IsSinglePlayer())
|
||||
v.escapeMenu, v.mapRenderer, v, v.terminal, v.uiManager, v.keyMap, v.audioProvider, v.logLevel,
|
||||
v.gameClient.IsSinglePlayer())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -124,6 +124,7 @@ func NewGameControls(
|
||||
term d2interface.Terminal,
|
||||
ui *d2ui.UIManager,
|
||||
keyMap *KeyMap,
|
||||
audioProvider d2interface.AudioProvider,
|
||||
l d2util.LogLevel,
|
||||
isSinglePlayer bool,
|
||||
) (*GameControls, error) {
|
||||
@ -207,7 +208,7 @@ func NewGameControls(
|
||||
inventoryRecord := asset.Records.Layout.Inventory[inventoryRecordKey]
|
||||
|
||||
heroStatsPanel := NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, l, hero.Stats)
|
||||
questLog := NewQuestLog(asset, ui, l, hero.Act)
|
||||
questLog := NewQuestLog(asset, ui, l, audioProvider, hero.Act)
|
||||
|
||||
inventory, err := NewInventory(asset, ui, l, hero.Gold, inventoryRecord)
|
||||
if err != nil {
|
||||
@ -732,6 +733,7 @@ func (g *GameControls) Advance(elapsed float64) error {
|
||||
g.mapRenderer.Advance(elapsed)
|
||||
g.hud.Advance(elapsed)
|
||||
g.inventory.Advance(elapsed)
|
||||
g.questLog.Advance(elapsed)
|
||||
|
||||
if err := g.escapeMenu.Advance(elapsed); err != nil {
|
||||
return err
|
||||
|
@ -51,6 +51,8 @@ const (
|
||||
questTabXOffset = 61
|
||||
)
|
||||
|
||||
const questCompleteAnimationDuration = 3
|
||||
|
||||
func (s *QuestLog) getPositionForSocket(number int) (x, y int) {
|
||||
pos := []struct {
|
||||
x int
|
||||
@ -71,6 +73,7 @@ func (s *QuestLog) getPositionForSocket(number int) (x, y int) {
|
||||
func NewQuestLog(asset *d2asset.AssetManager,
|
||||
ui *d2ui.UIManager,
|
||||
l d2util.LogLevel,
|
||||
audioProvider d2interface.AudioProvider,
|
||||
act int) *QuestLog {
|
||||
originX := 0
|
||||
originY := 0
|
||||
@ -79,12 +82,12 @@ func NewQuestLog(asset *d2asset.AssetManager,
|
||||
qs := map[int]int{
|
||||
0: -2,
|
||||
1: -2,
|
||||
2: -1,
|
||||
2: -2,
|
||||
3: 0,
|
||||
4: 1,
|
||||
5: 2,
|
||||
5: 4,
|
||||
6: 3,
|
||||
7: 0,
|
||||
7: -1,
|
||||
8: 0,
|
||||
9: 0,
|
||||
10: 0,
|
||||
@ -106,9 +109,9 @@ func NewQuestLog(asset *d2asset.AssetManager,
|
||||
26: 0,
|
||||
}
|
||||
|
||||
var quests [d2enum.ActsNumber]*d2ui.WidgetGroup
|
||||
var quests [d2enum.ActsNumber]*questEntire
|
||||
for i := 0; i < d2enum.ActsNumber; i++ {
|
||||
quests[i] = ui.NewWidgetGroup(d2ui.RenderPriorityQuestLog)
|
||||
quests[i] = &questEntire{WidgetGroup: ui.NewWidgetGroup(d2ui.RenderPriorityQuestLog)}
|
||||
}
|
||||
|
||||
var tabs [d2enum.ActsNumber]questLogTab
|
||||
@ -129,6 +132,7 @@ func NewQuestLog(asset *d2asset.AssetManager,
|
||||
quests: quests,
|
||||
questStatus: qs,
|
||||
maxPlayersAct: mpa,
|
||||
audioProvider: audioProvider,
|
||||
}
|
||||
|
||||
ql.Logger = d2util.NewLogger()
|
||||
@ -149,10 +153,12 @@ type QuestLog struct {
|
||||
selectedQuest int
|
||||
act int
|
||||
tab [d2enum.ActsNumber]questLogTab
|
||||
audioProvider d2interface.AudioProvider
|
||||
completeSound d2interface.SoundEffect
|
||||
|
||||
questName *d2ui.Label
|
||||
questDescr *d2ui.Label
|
||||
quests [d2enum.ActsNumber]*d2ui.WidgetGroup
|
||||
quests [d2enum.ActsNumber]*questEntire
|
||||
questStatus map[int]int
|
||||
maxPlayersAct int
|
||||
|
||||
@ -163,6 +169,13 @@ type QuestLog struct {
|
||||
*d2util.Logger
|
||||
}
|
||||
|
||||
type questEntire struct {
|
||||
*d2ui.WidgetGroup
|
||||
icons []*d2ui.Sprite
|
||||
buttons []*d2ui.Button
|
||||
sockets []*d2ui.Sprite
|
||||
}
|
||||
|
||||
/* questIconTab returns path to quest animation using its
|
||||
act and number. From d2resource:
|
||||
QuestLogAQuestAnimation = "/data/global/ui/MENU/a%dq%d.dc6"*/
|
||||
@ -176,6 +189,11 @@ const (
|
||||
notStartedFrame = 26
|
||||
)
|
||||
|
||||
const (
|
||||
socketNormalFrame = 0
|
||||
socketHighlightedFrame = 1
|
||||
)
|
||||
|
||||
const questDescriptionLenght = 30
|
||||
|
||||
type questLogTab struct {
|
||||
@ -189,6 +207,12 @@ func (s *QuestLog) Load() {
|
||||
|
||||
s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog)
|
||||
|
||||
// quest completion sound.
|
||||
s.completeSound, err = s.audioProvider.LoadSound(d2resource.QuestLogDoneSfx, false, false)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
|
||||
s.panelGroup.AddWidget(frame)
|
||||
|
||||
@ -198,7 +222,7 @@ func (s *QuestLog) Load() {
|
||||
}
|
||||
|
||||
w, h := frame.GetSize()
|
||||
staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticMenu, w, h)
|
||||
staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticPanelFrames, w, h)
|
||||
s.panelGroup.AddWidget(staticPanel)
|
||||
|
||||
closeButton := s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
|
||||
@ -227,18 +251,22 @@ func (s *QuestLog) Load() {
|
||||
|
||||
s.loadTabs()
|
||||
|
||||
// creates quest boards for each act
|
||||
for i := 0; i < d2enum.ActsNumber; i++ {
|
||||
s.quests[i] = s.loadQuestIconsForAct(i + 1)
|
||||
item, icons, buttons, sockets := s.loadQuestBoard(i + 1)
|
||||
s.quests[i] = &questEntire{item, icons, buttons, sockets}
|
||||
}
|
||||
|
||||
s.panelGroup.SetVisible(false)
|
||||
}
|
||||
|
||||
// loadTabs loads quest log tabs
|
||||
func (s *QuestLog) loadTabs() {
|
||||
var err error
|
||||
|
||||
tabsResource := d2resource.WPTabs
|
||||
|
||||
// create tabs only for 'discovered' acts
|
||||
for i := 0; i < s.maxPlayersAct; i++ {
|
||||
currentValue := i
|
||||
|
||||
@ -247,7 +275,8 @@ func (s *QuestLog) loadTabs() {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
// nolint:gomnd // it's constant
|
||||
// nolint:gomnd // it's constant.
|
||||
// each tab has two frames (active / inactive)
|
||||
frame := 2 * currentValue
|
||||
|
||||
err := s.tab[i].sprite.SetCurrentFrame(frame)
|
||||
@ -265,12 +294,15 @@ func (s *QuestLog) loadTabs() {
|
||||
s.panelGroup.AddWidget(s.tab[i].invisibleButton)
|
||||
}
|
||||
|
||||
// sets tab to current player's act.
|
||||
s.setTab(s.act - 1)
|
||||
}
|
||||
|
||||
func (s *QuestLog) loadQuestIconsForAct(act int) *d2ui.WidgetGroup {
|
||||
wg := s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog)
|
||||
// loadQuestBoard creates quest fields (socket, button, icon) for specified act
|
||||
func (s *QuestLog) loadQuestBoard(act int) (wg *d2ui.WidgetGroup, icons []*d2ui.Sprite, buttons []*d2ui.Button, sockets []*d2ui.Sprite) {
|
||||
wg = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog)
|
||||
|
||||
// sets number of quests in act (for act 4 it's only 3, else 6)
|
||||
var questsInAct int
|
||||
if act == d2enum.Act4 {
|
||||
questsInAct = d2enum.HalfQuestsNumber
|
||||
@ -278,12 +310,6 @@ func (s *QuestLog) loadQuestIconsForAct(act int) *d2ui.WidgetGroup {
|
||||
questsInAct = d2enum.NormalActQuestsNumber
|
||||
}
|
||||
|
||||
var sockets []*d2ui.Sprite
|
||||
|
||||
var buttons []*d2ui.Button
|
||||
|
||||
var icon *d2ui.Sprite
|
||||
|
||||
for n := 0; n < questsInAct; n++ {
|
||||
cw := n
|
||||
x, y := s.getPositionForSocket(n)
|
||||
@ -293,57 +319,69 @@ func (s *QuestLog) loadQuestIconsForAct(act int) *d2ui.WidgetGroup {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
socket.SetPosition(x+questOffsetX, y+iconOffsetY+2*questOffsetY)
|
||||
socket.SetPosition(x, y+iconOffsetY+questOffsetY)
|
||||
sockets = append(sockets, socket)
|
||||
|
||||
icon, err := s.makeQuestIconForAct(act, n, x, y)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
icons = append(icons, icon)
|
||||
|
||||
button := s.uiManager.NewButton(d2ui.ButtonTypeBlankQuestBtn, "")
|
||||
button.SetPosition(x+questOffsetX, y+questOffsetY)
|
||||
button.SetEnabled(s.questStatus[s.cordsToQuestID(act, cw)] != d2enum.QuestStatusNotStarted)
|
||||
buttons = append(buttons, button)
|
||||
|
||||
icon, err = s.makeQuestIconForAct(act, n)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
icon.SetPosition(x+questOffsetX, y+questOffsetY+iconOffsetY)
|
||||
wg.AddWidget(icon)
|
||||
}
|
||||
|
||||
for i := 0; i < questsInAct; i++ {
|
||||
currentQuest := i
|
||||
|
||||
// creates callback for quest button
|
||||
buttons[i].OnActivated(func() {
|
||||
var err error
|
||||
|
||||
// set normal (not-highlighted) frame for each quest socket
|
||||
for j := 0; j < questsInAct; j++ {
|
||||
err = sockets[j].SetCurrentFrame(0)
|
||||
err = sockets[j].SetCurrentFrame(socketNormalFrame)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
}
|
||||
if act-1 == s.selectedTab {
|
||||
err = sockets[currentQuest].SetCurrentFrame(1)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
// highlights appropriate socket
|
||||
err = sockets[currentQuest].SetCurrentFrame(socketHighlightedFrame)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
// sets quest labels
|
||||
s.onQuestClicked(currentQuest + 1)
|
||||
})
|
||||
}
|
||||
|
||||
// adds sockets to widget group
|
||||
for _, s := range sockets {
|
||||
wg.AddWidget(s)
|
||||
}
|
||||
|
||||
// adds buttons to widget group
|
||||
for _, b := range buttons {
|
||||
wg.AddWidget(b)
|
||||
}
|
||||
|
||||
// adds icons to widget group
|
||||
for _, i := range icons {
|
||||
wg.AddWidget(i)
|
||||
}
|
||||
|
||||
wg.SetVisible(false)
|
||||
|
||||
return wg
|
||||
return wg, icons, buttons, sockets
|
||||
}
|
||||
|
||||
func (s *QuestLog) makeQuestIconForAct(act, n int) (*d2ui.Sprite, error) {
|
||||
func (s *QuestLog) makeQuestIconForAct(act, n, x, y int) (*d2ui.Sprite, error) {
|
||||
iconResource := s.questIconsTable(act, n)
|
||||
|
||||
icon, err := s.uiManager.NewSprite(iconResource, d2resource.PaletteSky)
|
||||
@ -355,27 +393,56 @@ func (s *QuestLog) makeQuestIconForAct(act, n int) (*d2ui.Sprite, error) {
|
||||
case d2enum.QuestStatusCompleted:
|
||||
err = icon.SetCurrentFrame(completedFrame)
|
||||
case d2enum.QuestStatusCompleting:
|
||||
// that's not complet now
|
||||
// animation will be played after quest-log panel is opened (see s.playQuestAnimation)
|
||||
err = icon.SetCurrentFrame(0)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
icon.PlayForward()
|
||||
icon.SetPlayLoop(false)
|
||||
err = icon.SetCurrentFrame(completedFrame)
|
||||
s.questStatus[s.cordsToQuestID(act, n)] = d2enum.QuestStatusCompleted
|
||||
case d2enum.QuestStatusNotStarted:
|
||||
err = icon.SetCurrentFrame(notStartedFrame)
|
||||
default:
|
||||
err = icon.SetCurrentFrame(inProgresFrame)
|
||||
}
|
||||
|
||||
icon.SetPosition(x+questOffsetX, y+questOffsetY+iconOffsetY)
|
||||
|
||||
return icon, err
|
||||
}
|
||||
|
||||
// playQuestAnimations plays animations for quests (when status=questStatusCompleting)
|
||||
func (s *QuestLog) playQuestAnimations() {
|
||||
for j, i := range s.quests[s.selectedTab].icons {
|
||||
questID := s.cordsToQuestID(s.selectedTab+1, j)
|
||||
if s.questStatus[questID] == d2enum.QuestStatusCompleting {
|
||||
s.completeSound.Play()
|
||||
|
||||
// quest should be highlighted and it's label should be displayed
|
||||
s.quests[s.selectedTab].buttons[j].Activate()
|
||||
|
||||
i.SetPlayLength(questCompleteAnimationDuration)
|
||||
i.PlayForward()
|
||||
i.SetPlayLoop(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stopPlayedAnimation stops currently played animations and sets quests in
|
||||
// completing state to completed (should be used, when quest log is closing)
|
||||
func (s *QuestLog) stopPlayedAnimations() {
|
||||
// stops all played animations
|
||||
for j, i := range s.quests[s.selectedTab].icons {
|
||||
questID := s.cordsToQuestID(s.selectedTab+1, j)
|
||||
if s.questStatus[questID] == d2enum.QuestStatusCompleting {
|
||||
s.questStatus[questID] = d2enum.QuestStatusCompleted
|
||||
|
||||
err := i.SetCurrentFrame(completedFrame)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setQuestLabel loads quest labels text (title and description)
|
||||
func (s *QuestLog) setQuestLabel() {
|
||||
if s.selectedQuest == 0 {
|
||||
if s.selectedQuest == d2enum.QuestNone {
|
||||
s.questName.SetText("")
|
||||
s.questDescr.SetText("")
|
||||
|
||||
@ -386,7 +453,7 @@ func (s *QuestLog) setQuestLabel() {
|
||||
|
||||
status := s.questStatus[s.cordsToQuestID(s.selectedTab+1, s.selectedQuest)-1]
|
||||
switch status {
|
||||
case d2enum.QuestStatusCompleted:
|
||||
case d2enum.QuestStatusCompleted, d2enum.QuestStatusCompleting:
|
||||
s.questDescr.SetText(
|
||||
strings.Join(
|
||||
d2util.SplitIntoLinesWithMaxWidth(
|
||||
@ -394,42 +461,64 @@ func (s *QuestLog) setQuestLabel() {
|
||||
questDescriptionLenght),
|
||||
"\n"),
|
||||
)
|
||||
case d2enum.QuestStatusCompleting:
|
||||
s.questDescr.SetText("")
|
||||
case d2enum.QuestStatusNotStarted:
|
||||
s.questDescr.SetText("")
|
||||
default:
|
||||
s.questDescr.SetText(strings.Join(
|
||||
d2util.SplitIntoLinesWithMaxWidth(
|
||||
s.asset.TranslateString(
|
||||
fmt.Sprintf("qstsa%dq%d%d", s.selectedTab+1, s.selectedQuest, status),
|
||||
),
|
||||
questDescriptionLenght),
|
||||
"\n"),
|
||||
)
|
||||
str := fmt.Sprintf("qstsa%dq%d%d", s.selectedTab+1, s.selectedQuest, status)
|
||||
descr := s.asset.TranslateString(str)
|
||||
|
||||
// if description not found
|
||||
if str == descr {
|
||||
s.questDescr.SetText("")
|
||||
} else {
|
||||
s.questDescr.SetText(strings.Join(
|
||||
d2util.SplitIntoLinesWithMaxWidth(
|
||||
descr, questDescriptionLenght),
|
||||
"\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch all socket (in current tab) to normal state
|
||||
func (s *QuestLog) clearHighlightment() {
|
||||
for _, i := range s.quests[s.selectedTab].sockets {
|
||||
err := i.SetCurrentFrame(socketNormalFrame)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *QuestLog) setTab(tab int) {
|
||||
var mod int
|
||||
|
||||
// before we leafe current tab, we need to switch highlighted
|
||||
// quest socket to normal frame
|
||||
s.clearHighlightment()
|
||||
|
||||
s.selectedTab = tab
|
||||
s.selectedQuest = d2enum.QuestNone
|
||||
s.setQuestLabel()
|
||||
s.playQuestAnimations()
|
||||
|
||||
// displays appropriate quests board
|
||||
for i := 0; i < s.maxPlayersAct; i++ {
|
||||
s.quests[i].SetVisible(tab == i)
|
||||
}
|
||||
|
||||
// "highlights" appropriate tab
|
||||
for i := 0; i < s.maxPlayersAct; i++ {
|
||||
cv := i
|
||||
|
||||
// converts bool to 1/0
|
||||
if cv == s.selectedTab {
|
||||
mod = 0
|
||||
} else {
|
||||
mod = 1
|
||||
}
|
||||
|
||||
// sets tab sprite to highlighted/non-highlighted
|
||||
err := s.tab[cv].sprite.SetCurrentFrame(2*cv + mod)
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
@ -443,8 +532,9 @@ func (s *QuestLog) onQuestClicked(number int) {
|
||||
s.Infof("Quest number %d in tab %d clicked", number, s.selectedTab)
|
||||
}
|
||||
|
||||
//
|
||||
func (s *QuestLog) onDescrClicked() {
|
||||
//
|
||||
s.Info("Quest description button clicked")
|
||||
}
|
||||
|
||||
// IsOpen returns true if the hero status panel is open
|
||||
@ -466,6 +556,7 @@ func (s *QuestLog) Open() {
|
||||
s.isOpen = true
|
||||
s.panelGroup.SetVisible(true)
|
||||
s.setTab(s.selectedTab)
|
||||
s.playQuestAnimations()
|
||||
}
|
||||
|
||||
// Close closed the hero status panel
|
||||
@ -477,6 +568,8 @@ func (s *QuestLog) Close() {
|
||||
s.quests[i].SetVisible(false)
|
||||
}
|
||||
|
||||
s.stopPlayedAnimations()
|
||||
|
||||
s.onCloseCb()
|
||||
}
|
||||
|
||||
@ -487,11 +580,22 @@ func (s *QuestLog) SetOnCloseCb(cb func()) {
|
||||
|
||||
// Advance updates labels on the panel
|
||||
func (s *QuestLog) Advance(elapsed float64) {
|
||||
//
|
||||
}
|
||||
if !s.IsOpen() {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *QuestLog) renderStaticMenu(target d2interface.Surface) {
|
||||
s.renderStaticPanelFrames(target)
|
||||
for j, i := range s.quests[s.selectedTab].icons {
|
||||
questID := s.cordsToQuestID(s.selectedTab+1, j)
|
||||
if s.questStatus[questID] == d2enum.QuestStatusCompleting {
|
||||
if err := i.Advance(elapsed); err != nil {
|
||||
s.Error(err.Error())
|
||||
}
|
||||
|
||||
if i.GetCurrentFrame() == completedFrame {
|
||||
s.questStatus[questID] = d2enum.QuestStatusCompleted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:dupl // I think it is OK, to duplicate this function
|
||||
@ -538,25 +642,3 @@ func (s *QuestLog) cordsToQuestID(act, number int) int {
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
//nolint:deadcode,unused // I think, it will be used, if not, we can just remove it
|
||||
func (s *QuestLog) questIDToCords(id int) (act, number int) {
|
||||
act = 1
|
||||
|
||||
for i := 0; i < d2enum.ActsNumber; i++ {
|
||||
if id < d2enum.NormalActQuestsNumber {
|
||||
break
|
||||
}
|
||||
|
||||
act++
|
||||
|
||||
id -= d2enum.NormalActQuestsNumber
|
||||
}
|
||||
|
||||
number = id
|
||||
if act > d2enum.Act4 {
|
||||
number -= d2enum.HalfQuestsNumber
|
||||
}
|
||||
|
||||
return act, number
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user