MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

The role-playing games (I-X) that started it all and the various spin-offs (including Dark Messiah).
User avatar
BTB
Peasant
Peasant
Posts: 93
Joined: 21 Aug 2011
Location: Houston, TX
Contact:

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

Unread postby BTB » 02 Nov 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 02 Nov 2017, 13:35, edited 1 time in total.
"You don't have to be a vampire to die like one... bitch." -Simon Belmont

User avatar
J. M. Sower
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 21 Nov 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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 21 Nov 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
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 21 Nov 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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 21 Nov 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 22 Nov 2017, 19:42, edited 10 times in total.

Rodril
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 24 Nov 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
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 28 Nov 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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 29 Nov 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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 02 Dec 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 02 Dec 2017, 15:22, edited 3 times in total.

User avatar
J. M. Sower
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 03 Dec 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
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 04 Dec 2017, 04: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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 18 Feb 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 18 Feb 2018, 18:19, edited 7 times in total.

Rodril
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 19 Feb 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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 20 Feb 2018, 06: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 20 Feb 2018, 18:45, edited 2 times in total.

Rodril
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 23 Feb 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
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 24 Feb 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 25 Feb 2018, 12:56, edited 8 times in total.

Rodril
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 04 Mar 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 04 Mar 2018, 17:57, edited 1 time in total.

User avatar
J. M. Sower
Scout
Scout
Posts: 188
Joined: 25 Jan 2016

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

Unread postby J. M. Sower » 01 Apr 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 01 Apr 2018, 20:58, edited 1 time in total.

Rodril
Swordsman
Swordsman
Posts: 556
Joined: 18 Nov 2016

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

Unread postby Rodril » 02 Apr 2018, 04: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 44 guests