Commit 19d10d1616721e43596f95b4312fed6ff34a7bdd

Authored by DarkGod
2 parents dd6056f9 d3a91fa8

Merge branch 'better-chat-ui'

Showing 31 changed files with 269 additions and 20 deletions
... ... @@ -18,7 +18,6 @@
18 18 -- darkgod@te4.org
19 19
20 20 require "engine.class"
21   -require "engine.dialogs.Chat"
22 21 require "Json2"
23 22 local slt2 = require "slt2"
24 23
... ... @@ -26,6 +25,7 @@ local slt2 = require "slt2"
26 25 -- @classmod engine.Chat
27 26 module(..., package.seeall, class.make)
28 27
  28 +_M.chat_dialog = "engine.dialogs.Chat"
29 29 _M.chat_context_strings = {"#{italic}##LIGHT_GREEN#", "#LAST##{normal}#"}
30 30 _M.chat_bold_strings = {"#{bold}#", "#{normal}#"}
31 31
... ... @@ -413,7 +413,7 @@ function _M:invoke(id)
413 413 local hd = {"Chat:invoke", id = id or self.default_id }
414 414 self:triggerHook(hd)
415 415
416   - local d = engine.dialogs.Chat.new(self, hd.id, self.force_dialog_width or 500)
  416 + local d = require(self.chat_dialog).new(self, hd.id, self.force_dialog_width or 500)
417 417 game:registerDialog(d)
418 418 return d
419 419 end
... ...
... ... @@ -37,17 +37,29 @@ function _M:init(chat, id, width)
37 37 self.npc = chat.npc
38 38 self.player = chat.player
39 39 self.no_offscreen = "bottom"
40   - Dialog.init(self, self.npc.getName and self.npc:getName() or self.npc.name, width or 500, 400)
  40 + Dialog.init(self, self.force_title or (self.npc.getName and self.npc:getName() or self.npc.name), width or 500, 400)
41 41
  42 + self:generateList()
  43 +
  44 + self:makeUI()
  45 +
  46 + self.key:addCommands{
  47 + __TEXTINPUT = function(c)
  48 + if self.list and self.list.chars[c] then
  49 + self:use(self.list[self.list.chars[c]])
  50 + end
  51 + end,
  52 + }
  53 +end
  54 +
  55 +function _M:makeUI()
42 56 local xoff = 0
43 57 if self.show_portraits then
44 58 xoff = 64
45 59 end
46 60
47   - self:generateList()
48   -
49   - self.c_desc = Textzone.new{font=chat.dialog_text_font, width=self.iw - 10 - xoff, height=1, auto_height=true, text=self.text.."\n"}
50   - self.c_list = VariableList.new{font=chat.dialog_answer_font, width=self.iw - 10 - xoff, max_height=game.h * 0.70 - self.c_desc.h, list=self.list, fct=function(item) self:use(item) end, select=function(item) self:select(item) end}
  61 + self.c_desc = Textzone.new{font=self.chat.dialog_text_font, width=self.iw - 10 - xoff, height=1, auto_height=true, text=self.text.."\n", can_focus=false}
  62 + self.c_list = VariableList.new{font=self.chat.dialog_answer_font, width=self.iw - 10 - xoff, max_height=game.h * 0.70 - self.c_desc.h, list=self.list, fct=function(item) self:use(item) end, select=function(item) self:select(item) end}
51 63
52 64 local uis = {
53 65 {left=0, top=0, ui=self.c_desc},
... ... @@ -64,14 +76,6 @@ function _M:init(chat, id, width)
64 76 self:loadUI(uis)
65 77 self:setFocus(self.c_list)
66 78 self:setupUI(false, true)
67   -
68   - self.key:addCommands{
69   - __TEXTINPUT = function(c)
70   - if self.list and self.list.chars[c] then
71   - self:use(self.list[self.list.chars[c]])
72   - end
73   - end,
74   - }
75 79 end
76 80
77 81 function _M:on_register()
... ... @@ -121,7 +125,7 @@ function _M:use(item, a)
121 125 end
122 126
123 127 function _M:regen()
124   - local d = new(self.chat, self.cur_id, self.force_width)
  128 + local d = require(self.chat.chat_dialog).new(self.chat, self.cur_id, self.force_width)
125 129 d.__showup = false
126 130 game:replaceDialog(self, d)
127 131 self.next_dialog = d
... ...
... ... @@ -29,6 +29,8 @@ function _M:init(t)
29 29 self.actor = assert(t.actor, "no actorframe actor")
30 30 self.w = assert(t.w, "no actorframe w")
31 31 self.h = assert(t.h, "no actorframe h")
  32 + self.allow_cb = t.allow_cb
  33 + self.allow_shader = t.allow_shader
32 34 self.tiles = t.tiles or Tiles.new(self.w, self.h, nil, nil, true, nil)
33 35
34 36 Base.init(self, t)
... ... @@ -45,9 +47,9 @@ function _M:display(x, y, nb_keyframes, ox, oy)
45 47 local o = self.actor
46 48 if o and o.toScreen then
47 49 if o.image then
48   - o:toScreen(self.tiles, x, y, self.w, self.h)
  50 + o:toScreen(self.tiles, x, y, self.w, self.h, nil, self.allow_cb, self.allow_shader)
49 51 elseif o.image and o.add_mos then
50   - o:toScreen(self.tiles, x, y - h, self.w, self.h * 2)
  52 + o:toScreen(self.tiles, x, y - h, self.w, self.h * 2, nil, self.allow_cb, self.allow_shader)
51 53 end
52 54 end
53 55
... ...
... ... @@ -399,6 +399,8 @@ function _M:init(title, w, h, x, y, alpha, font, showup, skin)
399 399 self.frame.ox2 = self.frame.ox2 or conf.frame_ox2
400 400 self.frame.oy1 = self.frame.oy1 or conf.frame_oy1
401 401 self.frame.oy2 = self.frame.oy2 or conf.frame_oy2
  402 + if not self.force_min_w and conf.force_min_w then self.force_min_w = conf.force_min_w end
  403 + if not self.force_min_h and conf.force_min_h then self.force_min_h = conf.force_min_h end
402 404
403 405 if self.frame.dialog_h_middles then
404 406 local t = type(self.frame.dialog_h_middles) == "table" and table.clone(self.frame.dialog_h_middles) or {}
... ... @@ -589,6 +591,17 @@ function _M:setupUI(resizex, resizey, on_resize, addmw, addmh)
589 591
590 592 mh = mh + addh + 5 + 22 + 3 + (addmh or 0) + th + padding
591 593
  594 + if self.force_min_h then
  595 + mh = mh - self.frame.oy1 + self.frame.oy2
  596 + mh = math.max(mh, self.force_min_h)
  597 + mh = mh + self.frame.oy1 - self.frame.oy2
  598 + end
  599 + if self.force_min_w then
  600 + mw = mw - self.frame.ox1 + self.frame.ox2
  601 + mw = math.max(mw, self.force_min_w)
  602 + mw = mw + self.frame.ox1 - self.frame.ox2
  603 + end
  604 +
592 605 if on_resize then on_resize(resizex and mw or self.w, resizey and mh or self.h) end
593 606 nw, nh = resizex and mw or self.w, resizey and mh or self.h
594 607 else
... ...
... ... @@ -2078,11 +2078,14 @@ function _M:setupCommands()
2078 2078 print("===============")
2079 2079 end end,
2080 2080 [{"_g","ctrl"}] = function() if config.settings.cheat then
  2081 + package.loaded["engine.ui.Dialog"] = nil
2081 2082 package.loaded["engine.dialogs.Chat"] = nil
  2083 + package.loaded["mod.dialogs.Chat"] = nil
  2084 + package.loaded["mod.dialogs.elements.ChatPortrait"] = nil
2082 2085 package.loaded["engine.Chat"] = nil
2083 2086 local Chat = require "engine.Chat"
2084   - engine.dialogs.Chat.show_portraits = true
2085   - local chat = Chat.new("tareyal+test", game.player, game.player)
  2087 + Chat.chat_dialog = "mod.dialogs.Chat"
  2088 + local chat = Chat.new("tareyal+test", engine.Entity.new{name=_t"Imperium courrier", image="npc/undead_risen_mistress_vira.png"}, game.player)
2086 2089 chat:invoke()
2087 2090 do return end
2088 2091 DamageType:get(DamageType.ACID).projector(game.player, game.player.x, game.player.y, DamageType.ACID, 100)
... ...
... ... @@ -423,6 +423,7 @@ function _M:act()
423 423 if self:enoughEnergy() then
424 424 game.paused = true
425 425 if game.uiset.logdisplay:getNewestLine() ~= "" then game.log("") end
  426 + if config.settings.cheat then game.log("Game Turn %d", game.turn) end
426 427 end
427 428 elseif not self.player then
428 429 self:useEnergy()
... ...
  1 +-- ToME - Tales of Maj'Eyal
  2 +-- Copyright (C) 2009 - 2019 Nicolas Casalini
  3 +--
  4 +-- This program is free software: you can redistribute it and/or modify
  5 +-- it under the terms of the GNU General Public License as published by
  6 +-- the Free Software Foundation, either version 3 of the License, or
  7 +-- (at your option) any later version.
  8 +--
  9 +-- This program is distributed in the hope that it will be useful,
  10 +-- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +-- GNU General Public License for more details.
  13 +--
  14 +-- You should have received a copy of the GNU General Public License
  15 +-- along with this program. If not, see <http://www.gnu.org/licenses/>.
  16 +--
  17 +-- Nicolas Casalini "DarkGod"
  18 +-- darkgod@te4.org
  19 +
  20 +chat = {
  21 + frame_alpha = 1,
  22 + frame_darkness = 0.6,
  23 + frame_ox1 = -64,
  24 + frame_ox2 = 64,
  25 + frame_oy1 = -5,
  26 + frame_oy2 = 10,
  27 + -- force_min_w = 64 * 4,
  28 + -- force_min_h = 64 * 4,
  29 +}
... ...
  1 +-- TE4 - T-Engine 4
  2 +-- Copyright (C) 2009 - 2019 Nicolas Casalini
  3 +--
  4 +-- This program is free software: you can redistribute it and/or modify
  5 +-- it under the terms of the GNU General Public License as published by
  6 +-- the Free Software Foundation, either version 3 of the License, or
  7 +-- (at your option) any later version.
  8 +--
  9 +-- This program is distributed in the hope that it will be useful,
  10 +-- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +-- GNU General Public License for more details.
  13 +--
  14 +-- You should have received a copy of the GNU General Public License
  15 +-- along with this program. If not, see <http://www.gnu.org/licenses/>.
  16 +--
  17 +-- Nicolas Casalini "DarkGod"
  18 +-- darkgod@te4.org
  19 +
  20 +require "engine.class"
  21 +local Chat = require "engine.dialogs.Chat"
  22 +local VariableList = require "engine.ui.VariableList"
  23 +local Textzone = require "engine.ui.Textzone"
  24 +local Separator = require "engine.ui.Separator"
  25 +local ChatPortrait = require "mod.dialogs.elements.ChatPortrait"
  26 +local Map = require "engine.Map"
  27 +local Entity = require "engine.Entity"
  28 +
  29 +module(..., package.seeall, class.inherit(Chat))
  30 +
  31 +function _M:init(chat, id, width)
  32 + self.ui = "chat"
  33 + self.force_title = ""
  34 + self.force_min_h = 256
  35 +
  36 + Chat.init(self, chat, id, math.max(width, game.w * 0.4))
  37 +end
  38 +
  39 +function _M:makeUI()
  40 + self.c_desc = Textzone.new{has_box=true, ui="chat", font=self.chat.dialog_text_font, width=self.iw, height=1, auto_height=true, text=self.text, can_focus=false}
  41 + self.c_list = VariableList.new{font=self.chat.dialog_answer_font, width=self.iw, max_height=game.h * 0.70 - self.c_desc.h, list=self.list, fct=function(item) self:use(item) end, select=function(item) self:select(item) end}
  42 + local npc_frame = ChatPortrait.new{ui="chat", actor=self:getActorPortrait(self.npc.chat_display_entity or self.npc)}
  43 + local player_frame = ChatPortrait.new{ui="chat", actor=self:getActorPortrait(self.player.chat_display_entity or self.player)}
  44 +
  45 + local uis = {
  46 + {hcenter=0, top=-12, ui=self.c_desc},
  47 + {right=0, bottom=0, ui=self.c_list},
  48 + {left=-player_frame.w+self.frame.ox1-5, vcenter=-self.ix-4, ui=player_frame, ignore_size=true},
  49 + {right=-npc_frame.w-self.frame.ox2-5, vcenter=-self.ix-4, ui=npc_frame, ignore_size=true},
  50 + }
  51 +
  52 + self:loadUI(uis)
  53 + self:setFocus(self.c_list)
  54 + self:setupUI(false, true, function(w, h)
  55 + self.force_x = game.w / 2 - w / 2
  56 + self.force_y = game.h - h - 20
  57 + end)
  58 +end
  59 +
  60 +function _M:getActorPortrait(actor)
  61 + local actor = actor.replace_display or actor
  62 +
  63 + -- Moddable tiles are already portrait sized
  64 + if actor.moddable_tile and Map.tiles.no_moddable_tiles then return actor end
  65 +
  66 + -- No need for anything special
  67 + if actor.image:find("^portrait/") then return actor end
  68 +
  69 + -- Find the npc portrait
  70 + if actor.image == "invis.png" and actor.add_mos and actor.add_mos[1] and actor.add_mos[1].image and actor.add_mos[1].image:find("^npc/") and fs.exists("/data/gfx/shockbolt/"..actor.add_mos[1].image:gsub("^npc/", "portrait/")) then
  71 + return Entity.new{name=actor.name, image=actor.add_mos[1].image:gsub("^npc/", "portrait/")}
  72 + elseif actor.image:find("^npc/") and fs.exists("/data/gfx/shockbolt/"..actor.image:gsub("^npc/", "portrait/")) then
  73 + return Entity.new{name=actor.name, image=actor.image:gsub("^npc/", "portrait/")}
  74 + end
  75 +
  76 + -- Last resort, use it as it is
  77 + return actor
  78 +end
... ...
  1 +-- TE4 - T-Engine 4
  2 +-- Copyright (C) 2009 - 2019 Nicolas Casalini
  3 +--
  4 +-- This program is free software: you can redistribute it and/or modify
  5 +-- it under the terms of the GNU General Public License as published by
  6 +-- the Free Software Foundation, either version 3 of the License, or
  7 +-- (at your option) any later version.
  8 +--
  9 +-- This program is distributed in the hope that it will be useful,
  10 +-- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +-- GNU General Public License for more details.
  13 +--
  14 +-- You should have received a copy of the GNU General Public License
  15 +-- along with this program. If not, see <http://www.gnu.org/licenses/>.
  16 +--
  17 +-- Nicolas Casalini "DarkGod"
  18 +-- darkgod@te4.org
  19 +
  20 +require "engine.class"
  21 +local Tiles = require "engine.Tiles"
  22 +local Base = require "engine.ui.Base"
  23 +local ActorFrame = require "engine.ui.ActorFrame"
  24 +
  25 +--- A generic UI image
  26 +-- @classmod engine.ui.Image
  27 +module(..., package.seeall, class.inherit(Base))
  28 +
  29 +function _M:init(t)
  30 + assert(t.actor, "no ChatPortrait actor")
  31 +
  32 + self.name = t.actor.getName and t.actor:getName() or _t(t.actor.name)
  33 + if t.actor.moddable_tile then
  34 + self.actor_frame = ActorFrame.new{actor=t.actor, w=128, h=128, allow_cb=false, allow_shader=false}
  35 + elseif t.actor.image == "invis.png" and t.actor.add_mos and t.actor.add_mos[1] and t.actor.add_mos[1].image then
  36 + self.image = Tiles:loadImage(t.actor.add_mos[1].image)
  37 + else
  38 + self.image = Tiles:loadImage(t.actor.image)
  39 + end
  40 + if self.image then
  41 + local iw, ih = self.image:getSize()
  42 + if iw <= 64 then iw, ih = iw * 2, ih * 2 end
  43 + self.iw, self.ih = iw, ih
  44 + if self.image.getEmptyMargins then
  45 + local x1, x2, y1, y2 = self.image:getEmptyMargins()
  46 + self.iy = y1
  47 + else
  48 + self.iy = 0
  49 + end
  50 + else
  51 + self.iy = 0
  52 + self.iw, self.ih = 128, 128
  53 + end
  54 +
  55 + Base.init(self, t)
  56 +end
  57 +
  58 +function _M:generate()
  59 + self.mouse:reset()
  60 + self.key:reset()
  61 +
  62 + self.front = self:getUITexture("ui/portrait_frame_front.png")
  63 + self.back = self:getUITexture("ui/portrait_frame_back.png")
  64 + self.w, self.h = self.front.w, self.front.h
  65 +
  66 + if self.image then self.item = {self.image:glTexture(Tiles.sharp_scaling)} end
  67 +
  68 + self.name_tex = self:drawFontLine(self.font, self.name, nil, 0xff, 0xee, 0xcb)
  69 +end
  70 +
  71 +function _M:display(x, y, nb_keyframes, screen_x, screen_y)
  72 + self.back.t:toScreenFull(x, y, self.back.w, self.back.h, self.back.tw, self.back.th)
  73 + core.display.glScissor(true, screen_x + 15, screen_y + 15, 128, 192)
  74 + local dx, dy = x + 15 + (128 - self.iw) / 2, y + 15 + (192 - self.ih) / 2
  75 + if self.actor_frame then
  76 + self.actor_frame:display(dx, dy - self.iy)
  77 + elseif self.item then
  78 + self.item[1]:toScreen(dx, dy - self.iy, self.iw, self.ih)
  79 + end
  80 + core.display.glScissor(false)
  81 + self.front.t:toScreenFull(x, y, self.front.w, self.front.h, self.front.tw, self.front.th)
  82 +
  83 + core.display.glScissor(true, screen_x + 4, screen_y + 229, 152, 23)
  84 + -- Center if it fits, left align is not
  85 + if self.name_tex.w <= 229 then
  86 + self:textureToScreen(self.name_tex, x + 80 - self.name_tex.w / 2, y + 240 - self.name_tex.h / 2)
  87 + else
  88 + self:textureToScreen(self.name_tex, x + 4, y + 240 - self.name_tex.h / 2)
  89 + end
  90 + core.display.glScissor(false)
  91 +end
... ...
... ... @@ -243,6 +243,7 @@ Store:loadStores("/data/general/stores/basic.lua")
243 243
244 244 -- Configure chat dialogs
245 245 require("engine.dialogs.Chat").show_portraits = true
  246 +require("engine.Chat").chat_dialog = "mod.dialogs.Chat"
246 247
247 248 -- Inventory tabs
248 249 InventoryUI.default_tabslist = function(self)
... ...
... ... @@ -1809,6 +1809,32 @@ static int sdl_load_image_mem(lua_State *L)
1809 1809 return 3;
1810 1810 }
1811 1811
  1812 +static int sdl_find_empty_margins(lua_State *L) {
  1813 + SDL_Surface **ss = (SDL_Surface**)auxiliar_checkclass(L, "sdl{surface}", 1);
  1814 + SDL_Surface *s = *ss;
  1815 +
  1816 + int x1 = s->w, x2 = 0, y1 = s->h, y2 = 0;
  1817 +
  1818 + for (int x = 0; x < s->w; x++) {
  1819 + for (int y = 0; y < s->h; y++) {
  1820 + Uint32 *const target_pixel = (Uint32 *)((Uint8 *)s->pixels + y * s->pitch + x * s->format->BytesPerPixel);
  1821 + Uint8 r, g, b, a;
  1822 + SDL_GetRGBA(*target_pixel, s->format, &r, &g, &b, &a);
  1823 + if (a != 0) {
  1824 + if (x < x1) x1 = x;
  1825 + if (y > x2) x2 = x;
  1826 + if (y < y1) y1 = y;
  1827 + if (y > y2) y2 = y;
  1828 + }
  1829 + }
  1830 + }
  1831 + lua_pushnumber(L, x1);
  1832 + lua_pushnumber(L, x2);
  1833 + lua_pushnumber(L, y1);
  1834 + lua_pushnumber(L, y2);
  1835 + return 4;
  1836 +}
  1837 +
1812 1838 static int sdl_free_surface(lua_State *L)
1813 1839 {
1814 1840 SDL_Surface **s = (SDL_Surface**)auxiliar_checkclass(L, "sdl{surface}", 1);
... ... @@ -3534,6 +3560,7 @@ static const struct luaL_Reg sdl_surface_reg[] =
3534 3560 {"close", sdl_free_surface},
3535 3561 {"erase", sdl_surface_erase},
3536 3562 {"getSize", sdl_surface_get_size},
  3563 + {"getEmptyMargins", sdl_find_empty_margins},
3537 3564 {"merge", sdl_surface_merge},
3538 3565 {"toScreen", sdl_surface_toscreen},
3539 3566 {"toScreenWithTexture", sdl_surface_toscreen_with_texture},
... ...