MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

The role-playing games (I-X) that started it all and the various spin-offs (including Dark Messiah).

Moderator: Moderators

User avatar
BTB
Peasant
Peasant
Posts: 71
Joined: 21 Aug 2011
Location: Houston, TX
Contact:

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby BTB » Nov 2 2017, 11:53

Hey, everyone... just popping in for my annual checkup on how this utility is progressing. Last I left off, there were a couple of key features that I was interested in that had yet to see implementation:

-Ability to change effects of potions (i.e. make "Restore MP" a compound potion instead of a basic one).
-Ability to edit bonuses on artifacts/relics
-Ability to edit skill levels/schools of spells (i.e. make Charm a basic-level mind spell and make Stun a Mind spell instead of Earth)

Have either of these become possible over the last year or so?
Last edited by BTB on Nov 2 2017, 13:35, edited 1 time in total.
"You don't have to be a vampire to die like one... *****." -Simon Belmont

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Nov 21 2017, 12:14

Rodril wrote:Hello.
Have new tool, seems funny and useful - simple interface manager for MM8. It allows to create custom buttons, icons, texts and animations over or behind default interface, it does not allow to change existing elements, but, in general, by abusing events.Action we can just disable default interface and make new on top of it.

Here is link with description and example in it: https://www.dropbox.com/sh/rnikhe3nhbfc ... nUOfa?dl=0
Example is a simple reconstruction of MM67 alike NPC followers system in MM8. Readme does not describe it as well as it should, but i think script is pretty clear.

Yes! I was waiting for something like that very long time.

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Nov 21 2017, 18:02

Rodril, what if I want to create text in game interface (when you look through the eyes of the party) that uses variable (like vars.a)? When the script for that is typed in "function events.GameInitialized2()" game can't find what thing is my variable, because it doesn't exist yet.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Nov 21 2017, 19:44

When you execute CustomUI.CreateText function, it returns table of text's settings, it have field "Text", change it when vars.a changes. Attaching vars.a to text during it's creation won't work, text will stay static.
Use this script as example, put it into "...Scripts\General" folder, some comments are inside:
https://www.dropbox.com/s/5uke44zfejpot ... e.lua?dl=0

Link to last version of Interface manager:
https://www.dropbox.com/s/iis6dvilbq9na ... r.lua?dl=0

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Nov 21 2017, 21:05

Thanks for fast reply! I will check this tomorrow.

I wonder if there is posibility to make a graphical interpretation of some data, like the line of character HP (some numerical fraction). Your script allows that? I guess that will be possible with many graphics for every level of data, but I wonder about something more automatic, like one graphic showed in a part compared to the size of some numeric fraction. Some advice?

Edit: My script works now. Thanks!

Ps. How to make cyan color on icons transparent?

Ps2. I have a problem with polish characters ("ś" "ć" etc.) in StatusText used in function in Action of Icon (instead of them are showed some weird characters). StatusText used separately shows polish characters corectly. This is my code:

Code: Select all

Action = function() Game.ShowStatusText("Kościół Słońca") end

Anyway, is there any way to make showing some StatusText just by moving mouse over icon (without clicking)?
Last edited by J. M. Sower on Nov 22 2017, 19:42, edited 10 times in total.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Nov 24 2017, 20:43

J. M. Sower wrote:Ps. How to make cyan color on icons transparent?

It should be transparent by default, if .bmp encoded mm8-alike way. I don't know how exactly. Disabling color space information and using R5 G6 B5 color coding, during export from Gimp helps sometimes. If not, add "Masked = true" property into CustomUI.Create*Icon/Button* function, then color of upper left pixel of image will be used for transparent mask.
J. M. Sower wrote:I wonder if there is posibility to make a graphical interpretation of some data, like the line of character HP (some numerical fraction). Your script allows that? I guess that will be possible with many graphics for every level of data, but I wonder about something more automatic, like one graphic showed in a part compared to the size of some numeric fraction. Some advice?

Definetly it won't be easy, script does not provide any straight-forward instruments for that kind of tasks, but most of UI element fields can be binded to something and animated that way, like X, Y, source icon etc, use dump function in debug console to see them all (like "test = CustomUI.CreateIcon{*params*} ... dump(test)"). Also CustomUI.CreateIcon{*params*} allows to make animations, pass table of icon names to it's "Icon" property, and function which will return icon's index to "Animator" property. Default animator function is CustomUI.StdAnimator, which returns indexes in cycle. Each animator function gets "t" param - settings of current icon.
J. M. Sower wrote:Anyway, is there any way to make showing some StatusText just by moving mouse over icon (without clicking)?

Not for now, i'll add it.
J. M. Sower wrote: I have a problem with polish characters ("ś" "ć" etc.)

Maybe something related to font, don't know how to solve it, try to set "Layer" property of icon to 3 or 4 just to see if it can affect it anyhow.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Nov 28 2017, 19:49

Interface manager supports mouse over action for icons and buttons now, link is same. Add *MouseOverAction = function() ... end* property during creation of new element. Function will fire once each time when cursor enters into bounding box of element.

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Nov 29 2017, 20:47

Rodril wrote:Interface manager supports mouse over action for icons and buttons now, link is same. Add *MouseOverAction = function() ... end* property during creation of new element. Function will fire once each time when cursor enters into bounding box of element.

Great! But did I understand good that it will show StatusText once for limited time during mouse entering over element? It is good, but I wonder about activating Status tex for all the time when mouse is over object. Anyway, thanks!

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Dec 2 2017, 14:53

Rodril wrote:
J. M. Sower wrote: I have a problem with polish characters ("ś" "ć" etc.)

Maybe something related to font, don't know how to solve it, try to set "Layer" property of icon to 3 or 4 just to see if it can affect it anyhow.

This problem occurs also for CreateText with polish characters. But this problem can be fixed just by replacing "Text" value from the level of game. Replacing "Action" value to function with ShowStatusText from the level of game gives nothing. That problem exist on every layer.

Edit: Hmm... it works when I wrote eg. "Game.ShowStatusText(evt.str[34])" and this string have polish characters.

Edit2: I used this script in "Globals" and all "CreateText" have assigned this strings. Maybe this is not professional but it works. ;)

Code: Select all

function events.AfterLoadMap()
evt.str[300] = "Kościół Słońca"
evt.str[301] = "Kościół Księżyca"
evt.str[302] = "Królestwo Karigoru"
evt.str[303] = "Czerwone Krasnoludy"
evt.str[304] = "Nekromanci"
evt.str[305] = "Syreny"
evt.str[306] = "Piraci"
evt.str[307] = "Elfickie Królestwo Vori"
evt.str[308] = "Państwo Tatalii"
end
Last edited by J. M. Sower on Dec 2 2017, 15:22, edited 3 times in total.

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Dec 3 2017, 14:45

Rodril, I copied "RemoveNPCTablesLimits.lua" from your Merge mod and after start of new game I have this error message. What is wrong? I realy need bigger count of npc topics. :/

Code: Select all

...II moded\Scripts\Structs\After\LocalizationAndQuests.lua:295: array index (998) out of bounds [1, 753]

stack traceback:
   [C]: in function 'error'
   Scripts/Core/RSMem.lua:1342: in function '__index'
   ...II moded\Scripts\Structs\After\LocalizationAndQuests.lua:295: in function 'UpdateCurrentQuests'
   ...II moded\Scripts\Structs\After\LocalizationAndQuests.lua:312: in function 'v'
   Scripts/Core/EventsList.lua:68: in function <Scripts/Core/EventsList.lua:63>

arguments of '__index':
   t = (table: 0x07d6c1a8)
   a = 998
   v = nil

local variables of '__index':
   aorig = 998
   a1 = (table: 0x02a9c750)
   n = 753

upvalues of '__index':
   ptr = nil
   u4 = (table: 0x02a78fd0)
   GetPtr = (function: 0x02a77240)
   obj = (table: 0x031a6368)
   o = 47040976
   assertnum = (function: 0x02c3c7e0)
   error = (function: 0x02aa0d70)
   type = (function: builtin#3)
   SetLen = nil
   low = 1
   GetLen = (function: 0x037c0480)
   lenP = nil
   lenA = nil
   count = 753
   size = 8
   _index = nil
   _newindex = nil
   tonumber = (function: builtin#17)
   beyondLen = nil
   f = (function: 0x02c2e8a8)
   format = (function: builtin#91)
   sOutOfBounds = "array index (%s) out of bounds [%s, %s]"
   tostring = (function: builtin#18)
   tostring2 = (function: 0x02c3c1b8)

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Dec 4 2017, 4:21

During limits removal script sets actual number of lines in npctopic.txt as new limit, fill 753 - 1001 lines in npctopic.txt with "placeholders" for future usage. MMExtension uses 995, 996, 997, 998, 999 and 1000 topics for it's purposes, those topics should exist and be reserved.


User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Feb 18 2018, 14:15

Rodril, I wrote small addon to your InterfaceManager script, that allows easy creating some bars (you can create even new HP bar or something). But I can't create possibility to changing values from game level. :/ Maybe you can fix that. This is the code. Just add it into your script.

Code: Select all

local function CreateBar(p)
 if #p.icons > 0 then
   if p.baseFrame == true then
      local range = p.var_max / (#p.icons - 1)
      local move = range / 2
   
      p.cricon = {}
      for i=1, #p.icons, 1 do
         local m = (i * range ) - move
         local w = m - range
         
         local cond1, cond2
         
         if m < p.var_max then
            cond1 = function() return p.value_var() < m end
         else
            cond1 = function() return p.value_var() <= p.var_max end
         end
         
         if w > p.var_min then
            cond2 = function() return p.value_var() >= w end
         else
            cond2 = function() return p.value_var() >= p.var_min end
         end
         
         p.cricon[i] = CustomUI.CreateIcon{
                     Icon = p.icons[i],
                     X = p.x,
                     Y = p.y,
                     Condition = function() return p.general_condition() and (cond1() and cond2()) end,
                     Layer = p.layer,
                     Screen = p.screen}
      end
   else
      local range = p.var_max / #p.icons
      
      for i=1, #p.icons, 1 do
         local m = i * range
         local w = m - range
         
         local cond1, cond2
         
         if m < p.var_max then
            cond1 = function() return p.value_var() < m end
         else
            cond1 = function() return p.value_var() <= p.var_max end
         end
         
         if w > p.var_min then
            cond2 = function() return p.value_var() >= w end
         else
            cond2 = function() return p.value_var() >= p.var_min end
         end
         
         p.cricon[i] = CustomUI.CreateIcon{
                     Icon = p.icons[i],
                     X = p.x,
                     Y = p.y,
                     Condition = function() return p.general_condition() and (cond1() and cond2()) end,
                     Layer = p.layer,
                     Screen = p.screen}
      end
   end
 end
end
CustomUI.CreateBar = CreateBar


And here you have my code for some bar for example (but it usues my own graphics so you must use other for testing).

Code: Select all

repA = CustomUI.CreateBar{
      icons = {"rep00", "rep01", "rep02", "rep03", "rep04", "rep05", "rep06", "rep07", "rep08", "rep09", "rep10"},
      baseFrame = true, -- with icon when bar is empty. Type "false" for simpler bar
      x = 0,
      y = 0,
      layer = 1,
      screen = 0,
      general_condition = function() return RepPanelOpen end, -- main condition for all frames
      value_var = function() return repa end, -- variable used
      var_min = 0, -- maximal variable value
      var_max = 100, -- minimal variable value
}


Ps. And screenshot. That bars on left are created with my "CustomUI.CreateBar"
Image
Last edited by J. M. Sower on Feb 18 2018, 18:19, edited 7 times in total.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Feb 19 2018, 23:38

Hello. In your example diffrent variables are used: "repA" at base and "repa" in condition, these are case sensitive, i've changed it, and it allows to control bar's status. Also "repA = CustomUI.CreateBar{..." construction is not necessary, because this function does not return anything, and if "repA" had any value, it will be changed to nil. Maybe I've understood wrong, could you control your bar by "repA" or was it just unconvenient?
I would suggest to let function "CreateBar" return table with controls: it should consist of all created icons, so we can get access directly to them, also "repA" variable should be field of this table, so it won't cross names of other variables. I will write example later, if you want, need a bit more time.

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Feb 20 2018, 6:01

Rodril wrote:Hello. In your example diffrent variables are used: "repA" at base and "repa" in condition, these are case sensitive, i've changed it, and it allows to control bar's status. Also "repA = CustomUI.CreateBar{..." construction is not necessary, because this function does not return anything, and if "repA" had any value, it will be changed to nil. Maybe I've understood wrong, could you control your bar by "repA" or was it just unconvenient?
I would suggest to let function "CreateBar" return table with controls: it should consist of all created icons, so we can get access directly to them, also "repA" variable should be field of this table, so it won't cross names of other variables. I will write example later, if you want, need a bit more time.

So I will be waiting for your proposition.

Edit: "repA" is just the variable created to assign to it the data of that bar. "repa" is my global variable that I created to use the value of my "vars.repa" variable in interface (vars.[...] cannot be used straight in interface so I write something like this: "function events.FGInterfaceUpd() repa = vars.repa end"). So:
repA - data of the bar
repa - value used in the bar, dynamically copied from vars.repa
Last edited by J. M. Sower on Feb 20 2018, 18:45, edited 2 times in total.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Feb 23 2018, 12:10

I think it should look like this: https://www.dropbox.com/s/odrwuy2kft29w ... s.lua?dl=0
Function returns table with settings, which we can tweak any time without declaring extra global variables.

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Feb 24 2018, 10:32

Hmm... something is wrong with RepBar:GetState(). It always returns "false".

Edit: There is another problem but I can't find the cause. With your script bar disappears if the "Value" equals "0". I think maybe good solution would be "cond1" equal always "true" for last icon, and "cond2" always "true" for first.

It should look like that:

Code: Select all

ocal function CreateBar(p)

   if #p.icons == 0 then
      return
   end

   local Settings = {
      Icons = {},
      Toggle     = ToggleBar, -- Settings:Toggle(State) -- use : instead of . for these functions.
      Remove     = RemoveBar,
      GetState  = GetBarState,
      SetCoords = SetBarCoords,
      Condition = p.general_condition,
      Value    = p.Value or 0, -- Current bar's value.
      VMax   = p.var_max,
      VMin   = p.var_min,
      Key    = p.Key or "Bar_" .. p.X .. "_" .. p.Y
      }

   local range = p.var_max / (#p.icons - (p.baseFrame and 1 or 0))
   local move = p.baseFrame and range / 2 or 0

   for i = 1, #p.icons, 1 do

      local m = (i * range ) - move
      local w = m - range
      local cond1, cond2, mCond
      
      if i == 1 then
         cond1 = function() return Settings.Value < m end
         cond2 = function() return true end
      elseif i == #p.icons then
         cond1 = function() return true end
         cond2 = function() return Settings.Value >= w end
      else
         if m < Settings.VMax then
            cond1 = function() return Settings.Value < m end
         else
            cond1 = function() return Settings.Value <= Settings.VMax end
         end

         if w > Settings.VMin then
            cond2 = function() return Settings.Value >= w end
         else
            cond2 = function() return Settings.Value >= Settings.VMin end
         end
      end
      
      if Settings.Condition then
         mCond = function() return Settings.Condition() and (cond1() and cond2()) end
      else
         mCond = function() return cond1() and cond2() end
      end

      Settings.Icons[i] = CustomUI.CreateIcon{
         Active    = true,
         Icon    = p.icons[i],
         X       = p.X,
         Y       = p.Y,
         Condition    = mCond,
         Layer       = p.layer,
         Screen       = p.screen}
   end

   Bars[Settings.Key] = Settings
   return Settings -- use this structure to operate bar.

end
CustomUI.CreateBar = CreateBar

-- RepBar = CustomUI.CreateBar{...}
-- RepBar:GetState()
-- RepBar:SetCoords(X, Y)
-- RepBar:Toggle(true)
-- RepBar.Value = 50
-- RepBar:Remove()

-- for dynamic update:

--~ function events.FGInterfaceUpd()
--~    if RepBar:GetState() then -- better to keep this condition if source for bar stored in vars.
--~       RepBar.Value = Party.Reputation -- write here source of Bar's value.
--~    end
--~ end

-- Inspect possibilities to update bar during special events, for example, when player opens bar window, or only when specific value updates.


Edit2: I found another defect. Something is wrong with automatic determining icons width and height in CustomUI.CreateButton (my button had too high and too narrow field of mouse over). So at this time I have added possibility to set "Width" and "Height" manually. Here is my newest version of "InterfaceManager.lua":

Code: Select all

---- Base functions
CustomUI = {}
local Params = mem.StaticAlloc(80)

local PicStructPtr = Params
local ImageNamePtr = Params + 8
local PicX = Params + 16
local PicY = Params + 24

local Font = Params + 32
local Text = Params + 36
local LShift = Params + 40
local TColor = Params + 44

local TextParams = Params + 48
--TextX
--TextY
--TextWidth
--TextHeight
--TextUnk1
--TextUnk2

local LoadIconAs = mem.asmproc([[

push 0
push 0
push 2
push dword[ds:]] .. ImageNamePtr .. [[]; ImageNamePtr
mov ecx, 0x70d3e8; Icons.LOD path
call absolute 0x410d70

lea eax, dword [ds:eax+eax*8]
lea eax, dword [ds:eax*8+0x70d624]

retn]]) -- returns pointer to icon struct.

local SetMaskAs = mem.asmproc([[

mov eax, dword [ds:]] .. PicStructPtr .. [[];
movzx edx, word [ds:eax+0x18]
movzx ecx, word [ds:eax+0x1a]
imul ecx, edx
mov eax, dword [ds:eax+0x30]
movzx edx, byte [ds:eax]
test edx, edx
je @end

@rep:
   cmp byte [ds:eax], dl
   jnz @next
   mov byte [ds:eax], 0

   @next:
   inc eax
   dec ecx
   jnz @rep

@end:
retn

]]) -- replacing mask (upper left pixel) color with 00.

local UnloadIconAs = mem.asmproc([[

mov ecx, dword[ds:]] .. PicStructPtr .. [[];
call absolute 0x410a10

retn]]) -- removing loaded icon from memory and clearing it's appearance.

local ShowIconAs

ShowIconAs = mem.asmproc([[

mov eax, dword [ds:]] .. PicStructPtr .. [[];
cmp byte [ds:eax+0xe], 1
jnz @Std

push 0

@Std:
push dword[ds:]] .. PicStructPtr .. [[];
push dword[ds:]] .. PicY .. [[];
push dword[ds:]] .. PicX .. [[];
mov ecx, 0xec1980

je @trn

call absolute 0x4a3cd5
jmp @end

@trn:
call absolute 0x4a419b

@end:
retn]])

local ShowTextAs = mem.asmproc([[
pushfd
pushad

mov ecx, ]] .. TextParams .. [[;
xor esi, esi
mov eax, dword [ds:ecx-0xC];
mov edx, dword [ds:ecx-0x10]; Font
push dword [ds:ecx-0x8];   Shift between lines
push eax;    String
push dword [ds:ecx-0x4]; Color
push dword [ds:ecx-0x14];    Y offset (does not affect box)
push dword [ds:ecx-0x18];    X offset (does not affect box)
call absolute 0x44aae3

popad
popfd
retn]])

local function LoadIcon(Icon, Masked)
   local IconPtr
   if type(Icon) == "string" then
      mem.u4[ImageNamePtr] = mem.topointer(Icon)
   elseif type(Icon) == "number" then
      mem.u4[ImageNamePtr] = Icon
   else
      return false
   end
   IconPtr = mem.call(LoadIconAs)
   if Masked then
      mem.u4[PicStructPtr] = IconPtr
      mem.call(SetMaskAs)
      mem.u1[IconPtr+0xE] = 1
   end
   return IconPtr
end
CustomUI.LoadIcon = LoadIcon

local function UnloadIcon(IconPtr)
   if IconPtr and type(IconPtr) == "number" then
      mem.u4[PicStructPtr] = IconPtr
      return mem.call(UnloadIconAs)
   end
   return false
end
CustomUI.UnloadIcon = UnloadIcon

local function ShowIcon(Icon, X, Y)
   local IconPtr
   if type(Icon) == "string" then
      IconPtr = LoadIcon(Icon) -- in case icon is already in memory, function won't load it again, but return pointer to existing.
   elseif type(Icon) == "number" then
      IconPtr = Icon
   else
      return false
   end

   mem.u4[PicStructPtr] = IconPtr
   mem.i4[PicX] = X or 0
   mem.i4[PicY] = Y or 0

   mem.call(ShowIconAs)
end
CustomUI.ShowIcon = ShowIcon

local function ShowText(Str, Fnt, X, Y, Shift, R, G, B, BoxWt, BoxHt, Xof, Yof)

   if not Str then
      return false
   end

   if not Fnt or Fnt == 0 then
      Fnt = Game.Arrus_fnt
   end

   mem.u4[Text] = mem.topointer(Str)
   mem.u4[Font] = Fnt

   mem.i4[TextParams] = X or 0
   mem.i4[TextParams+0x4] = Y or 0
   mem.i4[TextParams+0x8] = BoxWt or 0
   mem.i4[TextParams+0xc] = BoxHt or 0

   mem.i4[PicX] = Xof or 0
   mem.i4[PicY] = Yof or 0

   mem.i4[LShift] = Shift or 3
   mem.i1[TColor] = B*16 + B
   mem.i1[TColor+1] = G + R*16

   mem.call(ShowTextAs)

end
CustomUI.ShowText = ShowText

local function MouseInBox(X,Y,W,H)
   return Mouse.X > X and Mouse.X < X + W and Mouse.Y > Y and Mouse.Y < Y + H
end
CustomUI.MouseInBox = MouseInBox

local function MouseInCircle(X,Y,R,Offset)
   if Offset then
      return R > math.sqrt((X+Offset-Mouse.X)^2 + (Y+Offset-Mouse.Y)^2)
   else
      return R > math.sqrt((X-Mouse.X)^2 + (Y-Mouse.Y)^2)
   end
end
CustomUI.MouseInCircle = MouseInCircle

-- "t" is animated icon (CreateIcon{})
local function StdAnimator(t)
   local CurT = timeGetTime()
   t.CurFrame = math.floor((CurT - t.StartTime)/t.Period + 1)
   if t.FramesCount < t.CurFrame then
      t.StartTime = CurT
      t.CurFrame = 1
   end
   return t.CurFrame
end
CustomUI.StdAnimator = StdAnimator

---- Elements
const.Screens.AdventurersInn = 29
const.Screens.Inventory2 = 15
const.Screens.SelectTarget = 20
const.Screens.SelectTarget2 = 23
local ActiveElements = {}
for k,v in pairs(const.Screens) do

   ActiveElements[v] = {}
   ActiveElements[v].Texts    = {[0] = {}, {}, {}, {}}
   ActiveElements[v].Icons      = {[0] = {}, {}, {}, {}}
   ActiveElements[v].Buttons    = {[0] = {}, {}, {}, {}}

end
CustomUI.ActiveElements = ActiveElements


--[[ t - structure of settings:
-- IconUp         - string - item name form icons.lod
-- IconDown         - string - item name form icons.lod
-- IconMouseOver   - string - item name form icons.lod
-- Action         - function
-- Condition      - function, which returns true if button can be shown
-- X            - number - upper left corner of image
-- Y            - number - upper left corner of image
-- Layer         - number (0 - front, 1 - middle, 2 - back, 3 - background)
-- Screen         - const.Screens or table
-- IsEllipse      - boolean - shape of button
-- Active         - boolean - true if button must appear right now.]]
local function CreateButton(t)

   local MainIcon = t.IconUp or t.IconMouseOver or t.IconDown
   if not MainIcon then return false end

   t.X = t.X or 0
   t.Y = t.Y or 0

   local Layer = t.Layer or 0
   local Key = MainIcon .. t.X .. t.Y .. "_" .. Layer
   local Chk = MouseInBox
   local Active = t.Active
   local IUpPtr, IDwPtr, IMoPtr
   local Width = t.Width
   local Height = t.Height

   if Active == nil then Active = true end

   IUpPtr = LoadIcon(t.IconUp or MainIcon, t.Masked)
   IDwPtr = LoadIcon(t.IconDown or MainIcon, t.Masked)
   if t.IconMouseOver then
      IMoPtr = LoadIcon(t.IconMouseOver or MainIcon, t.Masked)
   end
   
   if ( Width == nil ) and ( Height == nil ) then
      Width, Height = mem.u2[IUpPtr+0x18], mem.u2[IUpPtr+0x1a]
   end
   
   if t.IsEllipse then
      local R = Width/2
      Width  = R
      Height = R
      Chk = MouseInCircle
   end

   local Settings = {IUpPtr = IUpPtr, IDwPtr = IDwPtr, IMoPtr = IMoPtr,
                  IUpSrc = t.IconUp or MainIcon, IDwSrc = t.IconDown or MainIcon, IMoSrc = t.IconMouseOver,
                  Masked = t.Masked,
                  Act = t.Action, Cond = t.Condition,
                  X = t.X, Y = t.Y, Wt = Width, Ht = Height,
                  Layer    = Layer,
                  Screen   = t.Screen or 0,
                  Active    = Active,
                  Pressed = false,
                  MouseOver = false,
                  MOAct   = t.MouseOverAction,
                  Key    = Key,
                  Chk      = Chk,
                  Type    = "Button"}

   if type(t.Screen) == "table" then
      for k,v in pairs(t.Screen) do
         ActiveElements[v].Buttons[Layer][Key] = Settings
      end
   else
      ActiveElements[t.Screen or 0].Buttons[Layer][Key] = Settings
   end

   return Settings

end
CustomUI.CreateButton = CreateButton

--[[ Text     - string
-- Font       - Game. ... _fnt
-- X        - number
-- Y       - number
-- Unnecessary:
-- Layer    - 0 - front, 1 - middle, 2 - back, 3 - behind main interface
-- Screen    - const.Screens
-- Condition - function
-- ColorStd      - number - def == 0 - white
-- ColorMouseOver - number - def == 0 - white
-- Action        - function - on click
-- Active        - boolean]]
local function CreateText(t)

   if type(t) ~= "table" or not t.Text then
      return false
   end

   local LinesCount = table.maxn(string.split(t.Text, "\n"))
   local Active = t.Active

   if Active == nil then Active = true end

   local Settings = {Text = t.Text, X = t.X or 0, Y = t.Y or 0,
                  Wt = t.Width or math.floor(string.len(t.Text)*8/LinesCount),
                  Ht = t.Height or 10*LinesCount,
                  Layer = t.Layer or 0, Cond = t.Condition,
                  CStd = t.ColorStd or 0, CMo = t.ColorMouseOver or 0, Act = t.Action,
                  HvAct   = type(t.Action) == "function",
                  Pressed = false,
                  Font     = t.Font,
                  Shift    = t.Shift or 3,
                  R      = t.R or 0,
                  G      = t.G or 0,
                  B      = t.B or 0,
                  Rm      = t.Rm or 15,
                  Gm      = t.Gm or 15,
                  Bm      = t.Bm or 0,
                  Active    = Active,
                  Screen    = t.Screen or 0,
                  Xof    = 0,
                  Yof    = 0,
                  Type    = "Text"}

   local Layer = Settings.Layer
   local Key = string.sub(Settings.Text, 1, 3) .. Settings.X .. Settings.Y .. "_" .. Layer

   Settings.Key = Key

   if type(t.Screen) == "table" then
      for k,v in pairs(t.Screen) do
         ActiveElements[v].Texts[Layer][Key] = Settings
      end
   else
      ActiveElements[t.Screen or 0].Texts[Layer][Key] = Settings
   end

   return Settings

end
CustomUI.CreateText = CreateText

--[[ Icon    - string (for static) or table of strings (for animated)
-- Condition- function
-- X       - number
-- Y      - number
-- Layer   - number
-- Screen   - const.Screens
-- Active   - boolean
-- for animated:
-- Period   - number - to define animation speed (in StdAnimator: higher - slower)
-- Animator - function(t) - which will return frame number, "t" is access to icon settings. Default - StdAnimator
--]]--
local function CreateIcon(t)

   if not t.Icon then
      return false
   end

   local Icon, MainIcon
   local Settings = {}
   local IsAnim = type(t.Icon) == "table"
   local Width, Height
   local Active = t.Active

   if Active == nil then Active = true end

   if IsAnim then
      Icon = {}
      MainIcon = t.Icon[1]
      for i, v in ipairs(t.Icon) do
         Icon[i] = {Fr = 0x70d624, Src = v} -- LoadIcon(v, t.Masked)
      end
      Settings.Period = t.Period or 25
      Settings.FramesCount = table.maxn(t.Icon)
      Settings.CurFrame = 1
      Settings.StartTime = timeGetTime()
      Settings.CF = t.Animator or StdAnimator
      Width, Height = mem.u2[Icon[1].Fr+0x18], mem.u2[Icon[1].Fr+0x1a]
   else
      Icon = LoadIcon(t.Icon, t.Masked)
      MainIcon = t.Icon
      Width, Height = mem.u2[Icon+0x18], mem.u2[Icon+0x1a]
   end

   Settings.Icon    = Icon
   Settings.Masked = t.Masked
   Settings.Cond    = t.Condition
   Settings.X       = t.X or 0
   Settings.Y      = t.Y or 0
   Settings.Wt    = Width
   Settings.Ht      = Height
   Settings.Layer    = t.Layer or 0
   Settings.MainIcon = MainIcon
   Settings.Active = Active
   Settings.IsAnim = IsAnim
   Settings.Type    = "Icon"
   Settings.MouseOver = false
   Settings.MOAct = t.MouseOverAction

   local Layer = Settings.Layer
   local Key = MainIcon .. Settings.X .. Settings.Y .. "_" .. Layer

   Settings.Key = Key

   if type(t.Screen) == "table" then
      for k,v in pairs(t.Screen) do
         ActiveElements[v].Icons[Layer][Key] = Settings
      end
   else
      ActiveElements[t.Screen or 0].Icons[Layer][Key] = Settings
   end

   return Settings

end
CustomUI.CreateIcon = CreateIcon


---- Make global table for all bars to be able to access them any time.

local Bars = {}
CustomUI.ActiveElements.Bars = Bars -- ActiveElements also consisit of "Icons", "Buttons", "Texts" tables.

---- Make service functions for bars.

 -- Returns true if Bar is visible now
local function GetBarState(t)
   for k,v in pairs(t.Icons) do
      return v.Active and v.Cond() and Game.CurrentScreen == v.Screen
   end
end

 -- Switches Active field of all icons or sets it to "State" param if it is defined.
local function ToggleBar(t, State)
   local CurState = State ~= nil and State or not t:GetState()
   for k,v in pairs(t.Icons) do
      v.Active = CurState
   end
end

 -- Removes bar, unloads all icons.
local function RemoveBar(t)
   for k,v in pairs(t.Icons) do
      CustomUI.RemoveElement(v)
   end
   Bars[t.Key] = nil
   collectgarbage("collect")
end

 -- Move bar to defined coords.
local function SetBarCoords(t, X, Y)
   for k,v in pairs(t.Icons) do
      v.X = X or v.X
      v.Y = Y or v.Y
   end
end

----

local function CreateBar(p)

   if #p.icons == 0 then
      return
   end

   local Settings = {
      Icons = {},
      Toggle     = ToggleBar, -- Settings:Toggle(State) -- use : instead of . for these functions.
      Remove     = RemoveBar,
      GetState  = GetBarState,
      SetCoords = SetBarCoords,
      Condition = p.general_condition,
      Value    = p.Value or 0, -- Current bar's value.
      VMax   = p.var_max,
      VMin   = p.var_min,
      Key    = p.Key or "Bar_" .. p.X .. "_" .. p.Y
      }

   local range = p.var_max / (#p.icons - (p.baseFrame and 1 or 0))
   local move = p.baseFrame and range / 2 or 0

   for i = 1, #p.icons, 1 do

      local m = (i * range ) - move
      local w = m - range
      local cond1, cond2, mCond
      
      if i == 1 then
         cond1 = function() return Settings.Value < m end
         cond2 = function() return true end
      elseif i == #p.icons then
         cond1 = function() return true end
         cond2 = function() return Settings.Value >= w end
      else
         if m < Settings.VMax then
            cond1 = function() return Settings.Value < m end
         else
            cond1 = function() return Settings.Value <= Settings.VMax end
         end

         if w > Settings.VMin then
            cond2 = function() return Settings.Value >= w end
         else
            cond2 = function() return Settings.Value >= Settings.VMin end
         end
      end
      
      if Settings.Condition then
         mCond = function() return Settings.Condition() and (cond1() and cond2()) end
      else
         mCond = function() return cond1() and cond2() end
      end

      Settings.Icons[i] = CustomUI.CreateIcon{
         Active    = true,
         Icon    = p.icons[i],
         X       = p.X,
         Y       = p.Y,
         Condition    = mCond,
         Layer       = p.layer,
         Screen       = p.screen}
   end

   Bars[Settings.Key] = Settings
   return Settings -- use this structure to operate bar.

end
CustomUI.CreateBar = CreateBar

-- RepBar = CustomUI.CreateBar{...}
-- RepBar:GetState()
-- RepBar:SetCoords(X, Y)
-- RepBar:Toggle(true)
-- RepBar.Value = 50
-- RepBar:Remove()

-- for dynamic update:

--~ function events.FGInterfaceUpd()
--~    if RepBar:GetState() then -- better to keep this condition if source for bar stored in vars.
--~       RepBar.Value = Party.Reputation -- write here source of Bar's value.
--~    end
--~ end

-- Inspect possibilities to update bar during special events, for example, when player opens bar window, or only when specific value updates.


local function RemoveElement(t)

   if type(t) ~= "table" then
      return false
   end

   t.Active = false

   if t.Type == "Button" then
      UnloadIcon(t.IUpPtr)
      UnloadIcon(t.IDwPtr)
      UnloadIcon(t.IMoPtr)
   elseif t.Type == "Icon" then
      if t.Anim then
         for Ii, Iv in ipairs(v.Icon) do
            UnloadIcon(Iv.Fr)
         end
      else
         UnloadIcon(t.Icon)
      end
   end

   t = nil

   collectgarbage("collect")

   return true

end
CustomUI.RemoveElement = RemoveElement

local function ProcessButtons(la)
   local T = ActiveElements[Game.CurrentScreen].Buttons[la]
   for k, v in pairs(T) do
      if v.Active and (not v.Cond or v.Cond()) then

         if v.IUpSrc ~= mem.string(v.IUpPtr) then
            v.IUpPtr = LoadIcon(v.IUpSrc, v.Masked)
         end
         if v.IDwSrc ~= mem.string(v.IDwPtr) then
            v.IDwPtr = LoadIcon(v.IDwSrc, v.Masked)
         end
         if v.ImoSrc and v.IMoSrc ~= mem.string(v.IMoPtr) then
            v.IMoPtr = LoadIcon(v.IMoSrc, v.Masked)
         end

         if v.Chk(v.X,v.Y,v.Wt,v.Ht) then

            if Keys.IsPressed(const.Keys.LBUTTON) then
               ShowIcon(v.IDwPtr, v.X, v.Y)
               v.Pressed = true
            else
               ShowIcon(v.IMoPtr or v.IUpPtr, v.X, v.Y)
               if not v.MouseOver and v.MOAct then
                  v.MOAct()
               end
               if v.Pressed then
                  v.Act()
               end
               v.Pressed = false
            end

            v.MouseOver = true

         else
            ShowIcon(v.IUpPtr, v.X, v.Y)
            v.Pressed = false
            v.MouseOver = false
         end
      end
   end

end

local function ProcessTexts(la)
   local T = ActiveElements[Game.CurrentScreen].Texts[la]
   for k, v in pairs(T) do
      if v.Active and (not v.Cond or v.Cond()) then
         if v.HvAct then
            if MouseInBox(v.X,v.Y,v.Wt,v.Ht) then
               ShowText(v.Text, v.Font, v.X, v.Y, v.Shift, v.Rm, v.Gm, v.Bm, v.Wt, v.Ht, v.Xof, v.Yof)
               if Keys.IsPressed(const.Keys.LBUTTON) and not v.Pressed then
                  v.Act()
                  v.Pressed = true
               end
            else
               ShowText(v.Text, v.Font, v.X, v.Y, v.Shift, v.R, v.G, v.B, v.Wt, v.Ht, v.Xof, v.Yof)
               v.Pressed = false
            end
         else
            ShowText(v.Text, v.Font, v.X, v.Y, v.Shift, v.R, v.G, v.B, v.Wt, v.Ht, v.Xof, v.Yof)
         end
      end
   end
end


local function ProcessIcons(la)

   local T = ActiveElements[Game.CurrentScreen].Icons[la]

   for k,v in pairs(T) do

      if v.Active and (not v.Cond or v.Cond()) then

         if v.IsAnim then
            local CurF = v.Icon[v:CF()] or v.Icon[1]
            if CurF.Src ~= mem.string(CurF.Fr) then
               CurF.Fr = LoadIcon(CurF.Src, v.Masked)
            end
            ShowIcon(CurF.Fr, v.X, v.Y)

         else
            if v.MainIcon ~= mem.string(v.Icon) then
               v.Icon = LoadIcon(v.MainIcon, v.Masked)
            end
            ShowIcon(v.Icon, v.X, v.Y)

         end

         if v.MOAct then
            if MouseInBox(v.X,v.Y,v.Wt,v.Ht) then
               if not v.MouseOver then
                  v.MOAct()
               end
               v.MouseOver = true
            else
               v.MouseOver = false
            end
         end

      end
   end
end
---- Events

mem.autohook2(0x4d1d26, function()
   events.cocall("BGInterfaceUpd")
   ProcessIcons(3)
   ProcessTexts(3)
   ProcessButtons(3)

   events.cocall("L2InterfaceUpd")
   ProcessIcons(2)
   ProcessTexts(2)
   ProcessButtons(2)
end)

mem.autohook2(0x4a30a5, function(d)
   if mem.u4[d.eax] > 0 then
      ProcessIcons(1)
      ProcessTexts(1)
      ProcessButtons(1)
      events.cocall("L1InterfaceUpd")

      ProcessIcons(0)
      ProcessTexts(0)
      ProcessButtons(0)
      events.cocall("FGInterfaceUpd")
   end
end)


Edit3: ... and another defect. ;P If you will set some picture from EnglishD.lod as IconMouseOver for some CustomUI.CreateButton the button will disappear when mouse will be on it.

Also, I have a question. Is there any way to return some text from Global.txt?
Last edited by J. M. Sower on Feb 25 2018, 12:56, edited 8 times in total.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Mar 4 2018, 17:56

J. M. Sower wrote:Hmm... something is wrong with RepBar:GetState(). It always returns "false".

Ouch, there should be:
return v.Active and v.Cond() and Game.CurrentScreen == t.Screen
instead of v.Screen.
I'll add other fixes too.
J. M. Sower wrote:Edit3: ... and another defect. ;P If you will set some picture from EnglishD.lod as IconMouseOver for some CustomUI.CreateButton the button will disappear when mouse will be on it.

Script support only icons in icons.lod or *.icons.lod archives.
J. M. Sower wrote:Also, I have a question. Is there any way to return some text from Global.txt?

Yes, there is file "GlobalTxt.lua" inside "Scripts\Structs" folder in MM678 files, add it and you'll be able to use Game.GlobalTxt structure.
Also I've finally separated new events from "MiscTweaks.lua" into "ExtraEvents.lua" in general folder, inspect this if you want, it does not require any additional scripts for work.
Last edited by Rodril on Mar 4 2018, 17:57, edited 1 time in total.

User avatar
J. M. Sower
Pixie
Pixie
Posts: 141
Joined: 25 Jan 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby J. M. Sower » Apr 1 2018, 20:56

Thanks, but I have new questions. It is possible to add more lines into GlobalTxt than 750?

And I have a problem with new character dolls. I have dolls number 4 and 5, and they don't want to use pictures of armor with added "v5" and "v6" in the file name. Game always shows placeholder picture.
Last edited by J. M. Sower on Apr 1 2018, 20:58, edited 1 time in total.

Rodril
Scout
Scout
Posts: 172
Joined: 18 Nov 2016

Re: MMExtension v2.1 + MMEditor v2.1 Level Editor [Apr 22, 2016]

Postby Rodril » Apr 2 2018, 4:53

J. M. Sower wrote:Thanks, but I have new questions. It is possible to add more lines into GlobalTxt than 750?

No, limits of GlobalTxt have not been removed, why would you need it? I have not noticed any special functionality of GlobalTxt items, new lines can be stored in NPCText with same effect, or right in scripts, i think.
J. M. Sower wrote:And I have a problem with new character dolls. I have dolls number 4 and 5, and they don't want to use pictures of armor with added "v5" and "v6" in the file name. Game always shows placeholder picture.

There is outdated part of script at lines 1252 - 1257, it was supposed to shrink amount of required memory for new dolls by excluding from item-handle mechanics ones that can not use armors at all (like dragons). I have not played with new dolls a lot, can not say if excluding armorless dolls have any sense now.
To make it work: comment or remove 1252-1257 lines of code in RemoveItemsLimits.lua (or download one from mm678 files), don't forget to add extra columns to "Complex items pictures.txt" (t4, t5).


Return to “Might and Magic”

Who is online

Users browsing this forum: No registered users and 15 guests