2024-06-09 03:13:27 +08:00
local testing = require ( ' testing_util ' )
2021-07-10 13:43:53 +02:00
local core = require ( ' openmw.core ' )
local async = require ( ' openmw.async ' )
local util = require ( ' openmw.util ' )
2024-03-25 13:46:23 +00:00
local types = require ( ' openmw.types ' )
2024-09-14 10:10:01 +02:00
local vfs = require ( ' openmw.vfs ' )
2024-03-22 19:13:39 -05:00
local world = require ( ' openmw.world ' )
2024-10-20 09:37:27 +00:00
local I = require ( ' openmw.interfaces ' )
2021-07-10 13:43:53 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' timers ' , function ( )
2021-07-10 13:43:53 +02:00
testing.expectAlmostEqual ( core.getGameTimeScale ( ) , 30 , ' incorrect getGameTimeScale() result ' )
testing.expectAlmostEqual ( core.getSimulationTimeScale ( ) , 1 , ' incorrect getSimulationTimeScale result ' )
local startGameTime = core.getGameTime ( )
local startSimulationTime = core.getSimulationTime ( )
local ts1 , ts2 , th1 , th2
local cb = async : registerTimerCallback ( " tfunc " , function ( arg )
if arg == ' g ' then
th1 = core.getGameTime ( ) - startGameTime
else
ts1 = core.getSimulationTime ( ) - startSimulationTime
end
end )
async : newGameTimer ( 36 , cb , ' g ' )
async : newSimulationTimer ( 0.5 , cb , ' s ' )
async : newUnsavableGameTimer ( 72 , function ( )
th2 = core.getGameTime ( ) - startGameTime
end )
async : newUnsavableSimulationTimer ( 1 , function ( )
ts2 = core.getSimulationTime ( ) - startSimulationTime
end )
2024-06-22 02:52:02 +02:00
while not ( ts1 and ts2 and th1 and th2 ) do
coroutine.yield ( )
end
2021-07-10 13:43:53 +02:00
2022-06-06 01:10:33 +02:00
testing.expectGreaterOrEqual ( th1 , 36 , ' async:newGameTimer failed ' )
testing.expectGreaterOrEqual ( ts1 , 0.5 , ' async:newSimulationTimer failed ' )
testing.expectGreaterOrEqual ( th2 , 72 , ' async:newUnsavableGameTimer failed ' )
testing.expectGreaterOrEqual ( ts2 , 1 , ' async:newUnsavableSimulationTimer failed ' )
2025-02-26 23:12:17 +01:00
end )
2021-07-10 13:43:53 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' teleport ' , function ( )
2025-02-26 22:55:35 +01:00
local player = world.players [ 1 ]
2023-06-10 16:02:32 +02:00
player : teleport ( ' ' , util.vector3 ( 100 , 50 , 500 ) , util.transform . rotateZ ( math.rad ( 90 ) ) )
2021-07-10 13:43:53 +02:00
coroutine.yield ( )
testing.expect ( player.cell . isExterior , ' teleport to exterior failed ' )
testing.expectEqualWithDelta ( player.position . x , 100 , 1 , ' incorrect position after teleporting ' )
testing.expectEqualWithDelta ( player.position . y , 50 , 1 , ' incorrect position after teleporting ' )
2023-06-10 16:02:32 +02:00
testing.expectEqualWithDelta ( player.position . z , 500 , 1 , ' incorrect position after teleporting ' )
2024-06-22 02:58:30 +02:00
testing.expectEqualWithDelta ( player.rotation : getYaw ( ) , math.rad ( 90 ) , 0.05 , ' incorrect yaw rotation after teleporting ' )
testing.expectEqualWithDelta ( player.rotation : getPitch ( ) , math.rad ( 0 ) , 0.05 , ' incorrect pitch rotation after teleporting ' )
local rotationX1 , rotationZ1 = player.rotation : getAnglesXZ ( )
testing.expectEqualWithDelta ( rotationX1 , math.rad ( 0 ) , 0.05 , ' incorrect x rotation from getAnglesXZ after teleporting ' )
testing.expectEqualWithDelta ( rotationZ1 , math.rad ( 90 ) , 0.05 , ' incorrect z rotation from getAnglesXZ after teleporting ' )
local rotationZ2 , rotationY2 , rotationX2 = player.rotation : getAnglesZYX ( )
testing.expectEqualWithDelta ( rotationZ2 , math.rad ( 90 ) , 0.05 , ' incorrect z rotation from getAnglesZYX after teleporting ' )
testing.expectEqualWithDelta ( rotationY2 , math.rad ( 0 ) , 0.05 , ' incorrect y rotation from getAnglesZYX after teleporting ' )
testing.expectEqualWithDelta ( rotationX2 , math.rad ( 0 ) , 0.05 , ' incorrect x rotation from getAnglesZYX after teleporting ' )
2023-06-10 16:02:32 +02:00
player : teleport ( ' ' , player.position , { rotation = util.transform . rotateZ ( math.rad ( - 90 ) ) , onGround = true } )
coroutine.yield ( )
testing.expectEqualWithDelta ( player.rotation : getYaw ( ) , math.rad ( - 90 ) , 0.05 , ' options.rotation is not working ' )
testing.expectLessOrEqual ( player.position . z , 400 , ' options.onGround is not working ' )
2021-07-10 13:43:53 +02:00
player : teleport ( ' ' , util.vector3 ( 50 , - 100 , 0 ) )
coroutine.yield ( )
testing.expect ( player.cell . isExterior , ' teleport to exterior failed ' )
testing.expectEqualWithDelta ( player.position . x , 50 , 1 , ' incorrect position after teleporting ' )
testing.expectEqualWithDelta ( player.position . y , - 100 , 1 , ' incorrect position after teleporting ' )
2023-06-10 16:02:32 +02:00
testing.expectEqualWithDelta ( player.rotation : getYaw ( ) , math.rad ( - 90 ) , 0.05 , ' teleporting changes rotation ' )
2025-02-26 23:12:17 +01:00
end )
2021-07-10 13:43:53 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' getGMST ' , function ( )
2023-06-12 16:20:36 +02:00
testing.expectEqual ( core.getGMST ( ' non-existed gmst ' ) , nil )
testing.expectEqual ( core.getGMST ( ' Water_RippleFrameCount ' ) , 4 )
testing.expectEqual ( core.getGMST ( ' Inventory_DirectionalDiffuseR ' ) , 0.5 )
testing.expectEqual ( core.getGMST ( ' Level_Up_Level2 ' ) , ' something ' )
2025-02-26 23:12:17 +01:00
end )
2023-06-12 16:20:36 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' MWScript ' , function ( )
2024-03-22 19:13:39 -05:00
local variableStoreCount = 18
local variableStore = world.mwscript . getGlobalVariables ( player )
2024-03-23 13:27:53 -05:00
testing.expectEqual ( variableStoreCount , # variableStore )
2024-11-15 01:53:53 +01:00
2024-03-23 13:27:53 -05:00
variableStore.year = 5
testing.expectEqual ( 5 , variableStore.year )
2024-03-22 19:13:39 -05:00
variableStore.year = 1
local indexCheck = 0
for index , value in ipairs ( variableStore ) do
2024-03-23 13:27:53 -05:00
testing.expectEqual ( variableStore [ index ] , value )
2024-03-22 19:13:39 -05:00
indexCheck = indexCheck + 1
end
2024-03-23 13:27:53 -05:00
testing.expectEqual ( variableStoreCount , indexCheck )
2024-03-22 19:13:39 -05:00
indexCheck = 0
for index , value in pairs ( variableStore ) do
2024-03-23 13:27:53 -05:00
testing.expectEqual ( variableStore [ index ] , value )
2024-03-22 19:13:39 -05:00
indexCheck = indexCheck + 1
end
2024-03-23 13:27:53 -05:00
testing.expectEqual ( variableStoreCount , indexCheck )
2025-02-26 23:12:17 +01:00
end )
2024-03-22 19:13:39 -05:00
2024-06-22 02:52:02 +02:00
local function testRecordStore ( store , storeName , skipPairs )
2024-03-25 13:46:23 +00:00
testing.expect ( store.records )
local firstRecord = store.records [ 1 ]
2024-06-22 02:52:02 +02:00
if not firstRecord then
return
end
testing.expectEqual ( firstRecord.id , store.records [ firstRecord.id ] . id )
2024-03-25 13:46:23 +00:00
local status , _ = pcall ( function ( )
2024-06-22 02:52:02 +02:00
for index , value in ipairs ( store.records ) do
if value.id == firstRecord.id then
testing.expectEqual ( index , 1 , storeName )
break
end
end
2024-03-25 13:46:23 +00:00
end )
2024-06-22 02:52:02 +02:00
testing.expectEqual ( status , true , storeName )
2024-03-25 13:46:23 +00:00
end
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' record stores ' , function ( )
2024-03-25 13:46:23 +00:00
for key , type in pairs ( types ) do
if type.records then
2024-06-22 02:52:02 +02:00
testRecordStore ( type , key )
2024-03-25 13:46:23 +00:00
end
end
2024-06-22 02:52:02 +02:00
testRecordStore ( core.magic . enchantments , " enchantments " )
testRecordStore ( core.magic . effects , " effects " , true )
testRecordStore ( core.magic . spells , " spells " )
2024-03-25 13:46:23 +00:00
2024-06-22 02:52:02 +02:00
testRecordStore ( core.stats . Attribute , " Attribute " )
testRecordStore ( core.stats . Skill , " Skill " )
2024-03-25 13:46:23 +00:00
2024-06-22 02:52:02 +02:00
testRecordStore ( core.sound , " sound " )
testRecordStore ( core.factions , " factions " )
2024-03-25 13:46:23 +00:00
2024-06-22 02:52:02 +02:00
testRecordStore ( types.NPC . classes , " classes " )
testRecordStore ( types.NPC . races , " races " )
testRecordStore ( types.Player . birthSigns , " birthSigns " )
2025-02-26 23:12:17 +01:00
end )
2024-06-22 02:46:38 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' record creation ' , function ( )
2024-05-13 14:14:44 +00:00
local newLight = {
isCarriable = true ,
isDynamic = true ,
isFire = false ,
isFlicker = false ,
isFlickerSlow = false ,
isNegative = false ,
isOffByDefault = false ,
isPulse = false ,
weight = 1 ,
value = 10 ,
duration = 12 ,
radius = 30 ,
color = 5 ,
name = " TestLight " ,
2024-11-15 01:25:40 +01:00
model = " meshes/marker_door.dae "
2024-05-13 14:14:44 +00:00
}
local draft = types.Light . createRecordDraft ( newLight )
local record = world.createRecord ( draft )
for key , value in pairs ( newLight ) do
2024-06-22 02:52:02 +02:00
testing.expectEqual ( record [ key ] , value )
2024-05-13 14:14:44 +00:00
end
2025-02-26 23:12:17 +01:00
end )
2024-06-22 02:46:38 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' UTF-8 characters ' , function ( )
2024-06-22 02:46:38 +02:00
testing.expectEqual ( utf8.codepoint ( " 😀 " ) , 0x1F600 )
2024-06-09 03:13:27 +08:00
local chars = { }
for codepoint = 0 , 0x10FFFF do
local char = utf8.char ( codepoint )
local charSize = string.len ( char )
testing.expect ( not chars [ char ] , nil , " Duplicate UTF-8 character: " .. char )
chars [ char ] = true
if codepoint <= 0x7F then
testing.expectEqual ( charSize , 1 )
elseif codepoint <= 0x7FF then
testing.expectEqual ( charSize , 2 )
elseif codepoint <= 0xFFFF then
testing.expectEqual ( charSize , 3 )
elseif codepoint <= 0x10FFFF then
testing.expectEqual ( charSize , 4 )
end
testing.expectEqual ( utf8.codepoint ( char ) , codepoint )
testing.expectEqual ( utf8.len ( char ) , 1 )
end
2025-02-26 23:12:17 +01:00
end )
2024-06-22 02:46:38 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' UTF-8 strings ' , function ( )
2024-06-22 02:46:38 +02:00
local utf8str = " Hello, 你好, 🌎! "
2024-06-09 03:13:27 +08:00
local str = " "
for utf_char in utf8str : gmatch ( utf8.charpattern ) do
str = str .. utf_char
end
testing.expectEqual ( str , utf8str )
testing.expectEqual ( utf8.len ( utf8str ) , 13 )
testing.expectEqual ( utf8.offset ( utf8str , 9 ) , 11 )
2025-02-26 23:12:17 +01:00
end )
2024-06-22 02:46:38 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' memory limit ' , function ( )
2024-08-03 12:55:24 +02:00
local ok , err = pcall ( function ( )
local t = { }
local n = 1
while true do
t [ n ] = n
n = n + 1
end
end )
testing.expectEqual ( ok , false , ' Script reaching memory limit should fail ' )
testing.expectEqual ( err , ' not enough memory ' )
2025-02-26 23:12:17 +01:00
end )
2024-08-03 12:55:24 +02:00
2022-06-06 01:10:33 +02:00
local function initPlayer ( )
2025-02-26 22:55:35 +01:00
local player = world.players [ 1 ]
2024-08-17 15:09:00 +02:00
player : teleport ( ' ' , util.vector3 ( 4096 , 4096 , 1745 ) , util.transform . identity )
2022-06-06 01:10:33 +02:00
coroutine.yield ( )
2025-02-26 22:55:35 +01:00
return player
2022-06-06 01:10:33 +02:00
end
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' vfs ' , function ( )
2024-09-14 10:10:01 +02:00
local file = ' test_vfs_dir/lines.txt '
2025-01-02 15:55:19 +01:00
local nosuchfile = ' test_vfs_dir/nosuchfile '
2024-09-14 10:10:01 +02:00
testing.expectEqual ( vfs.fileExists ( file ) , true , ' lines.txt should exist ' )
2025-01-02 15:55:19 +01:00
testing.expectEqual ( vfs.fileExists ( nosuchfile ) , false , ' nosuchfile should not exist ' )
2024-09-14 10:10:01 +02:00
2025-01-02 15:55:19 +01:00
local expectedLines = { ' 1 ' , ' 2 ' , ' ' , ' 4 ' }
2024-09-14 10:10:01 +02:00
local getLine = vfs.lines ( file )
2025-01-02 15:55:19 +01:00
for _ , v in pairs ( expectedLines ) do
2024-09-14 10:10:01 +02:00
testing.expectEqual ( getLine ( ) , v )
end
testing.expectEqual ( getLine ( ) , nil , ' All lines should have been read ' )
local ok = pcall ( function ( )
2025-01-02 15:55:19 +01:00
vfs.lines ( nosuchfile )
2024-09-14 10:10:01 +02:00
end )
testing.expectEqual ( ok , false , ' Should not be able to read lines from nonexistent file ' )
local getPath = vfs.pathsWithPrefix ( ' test_vfs_dir/ ' )
testing.expectEqual ( getPath ( ) , file )
testing.expectEqual ( getPath ( ) , nil , ' All paths should have been read ' )
local handle = vfs.open ( file )
testing.expectEqual ( vfs.type ( handle ) , ' file ' , ' File should be open ' )
testing.expectEqual ( handle.fileName , file )
local n1 , n2 , _ , l3 , l4 = handle : read ( " *n " , " *number " , " *l " , " *line " , " *l " )
testing.expectEqual ( n1 , 1 )
testing.expectEqual ( n2 , 2 )
testing.expectEqual ( l3 , ' ' )
testing.expectEqual ( l4 , ' 4 ' )
testing.expectEqual ( handle : seek ( ' set ' , 0 ) , 0 , ' Reading should happen from the start of the file ' )
testing.expectEqual ( handle : read ( " *a " ) , ' 1 \n 2 \n \n 4 ' )
testing.expectEqual ( handle : close ( ) , true , ' File should be closeable ' )
testing.expectEqual ( vfs.type ( handle ) , ' closed file ' , ' File should be closed ' )
2025-01-02 15:55:19 +01:00
handle = vfs.open ( nosuchfile )
testing.expectEqual ( handle , nil , ' vfs.open should return nil on nonexistent files ' )
getLine = vfs.open ( file ) : lines ( )
for _ , v in pairs ( expectedLines ) do
testing.expectEqual ( getLine ( ) , v )
end
2025-02-26 23:12:17 +01:00
end )
2024-09-14 10:10:01 +02:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' commit crime ' , function ( )
2025-02-26 22:55:35 +01:00
local player = initPlayer ( )
2025-02-26 00:55:22 +01:00
testing.expectEqual ( player == nil , false , ' A viable player reference should exist to run `commit crime` ' )
2024-10-20 09:37:27 +00:00
testing.expectEqual ( I.Crimes == nil , false , ' Crimes interface should be available in global contexts ' )
-- Reset crime level to have a clean slate
2024-11-15 01:53:53 +01:00
types.Player . setCrimeLevel ( player , 0 )
2024-10-20 09:37:27 +00:00
testing.expectEqual ( I.Crimes . commitCrime ( player , { type = types.Player . OFFENSE_TYPE.Theft , victim = player , arg = 100 } ) . wasCrimeSeen , false , " Running the crime with the player as the victim should not result in a seen crime " )
testing.expectEqual ( I.Crimes . commitCrime ( player , { type = types.Player . OFFENSE_TYPE.Theft , arg = 50 } ) . wasCrimeSeen , false , " Running the crime with no victim and a type shouldn't raise errors " )
testing.expectEqual ( I.Crimes . commitCrime ( player , { type = types.Player . OFFENSE_TYPE.Murder } ) . wasCrimeSeen , false , " Running a murder crime should work even without a victim " )
-- Create a mockup target for crimes
local victim = world.createObject ( types.NPC . record ( player ) . id )
victim : teleport ( player.cell , player.position + util.vector3 ( 0 , 300 , 0 ) )
coroutine.yield ( )
-- Reset crime level for testing with a valid victim
2024-11-15 01:53:53 +01:00
types.Player . setCrimeLevel ( player , 0 )
2024-10-20 09:37:27 +00:00
testing.expectEqual ( I.Crimes . commitCrime ( player , { victim = victim , type = types.Player . OFFENSE_TYPE.Theft , arg = 50 } ) . wasCrimeSeen , true , " Running a crime with a valid victim should notify them when the player is not sneaking, even if it's not explicitly passed in " )
testing.expectEqual ( types.Player . getCrimeLevel ( player ) , 0 , " Crime level should not change if the victim's alarm value is low and there's no other witnesses " )
2025-02-26 23:12:17 +01:00
end )
2024-10-20 09:37:27 +00:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' record model property ' , function ( )
local player = world.players [ 1 ]
2024-11-15 01:25:40 +01:00
testing.expectEqual ( types.NPC . record ( player ) . model , ' meshes/basicplayer.dae ' )
2025-02-26 23:12:17 +01:00
end )
2024-11-15 01:53:53 +01:00
2025-02-26 23:12:17 +01:00
local function registerPlayerTest ( name )
testing.registerGlobalTest ( name , function ( )
2025-02-26 22:55:35 +01:00
local player = initPlayer ( )
2025-02-26 23:12:17 +01:00
testing.runLocalTest ( player , name )
end )
end
2025-02-26 00:55:22 +01:00
registerPlayerTest ( ' player yaw rotation ' )
registerPlayerTest ( ' player pitch rotation ' )
registerPlayerTest ( ' player pitch and yaw rotation ' )
registerPlayerTest ( ' player rotation ' )
registerPlayerTest ( ' player forward running ' )
registerPlayerTest ( ' player diagonal walking ' )
2025-02-26 23:12:17 +01:00
registerPlayerTest ( ' findPath ' )
registerPlayerTest ( ' findRandomPointAroundCircle ' )
registerPlayerTest ( ' castNavigationRay ' )
registerPlayerTest ( ' findNearestNavMeshPosition ' )
2025-02-26 00:55:22 +01:00
registerPlayerTest ( ' player memory limit ' )
2025-02-26 23:12:17 +01:00
2025-02-26 00:55:22 +01:00
testing.registerGlobalTest ( ' player weapon attack ' , function ( )
2025-02-26 23:12:17 +01:00
local player = initPlayer ( )
world.createObject ( ' basic_dagger1h ' , 1 ) : moveInto ( player )
2025-02-26 00:55:22 +01:00
testing.runLocalTest ( player , ' player weapon attack ' )
2025-02-26 23:12:17 +01:00
end )
2021-07-10 13:43:53 +02:00
2025-03-07 22:24:39 +01:00
testing.registerGlobalTest ( ' load while teleporting - init player ' , function ( )
local player = world.players [ 1 ]
player : teleport ( ' Museum of Wonders ' , util.vector3 ( 0 , - 1500 , 111 ) , util.transform . rotateZ ( math.rad ( 180 ) ) )
end )
testing.registerGlobalTest ( ' load while teleporting - teleport ' , function ( )
local player = world.players [ 1 ]
local landracer = world.createObject ( ' landracer ' )
landracer : teleport ( player.cell , player.position + util.vector3 ( 0 , 500 , 0 ) )
coroutine.yield ( )
local door = world.getObjectByFormId ( core.getFormId ( ' the_hub.omwaddon ' , 26 ) )
door : activateBy ( player )
coroutine.yield ( )
landracer : teleport ( player.cell , player.position )
end )
2021-07-10 13:43:53 +02:00
return {
engineHandlers = {
2025-02-26 00:55:22 +01:00
onUpdate = testing.updateGlobal ,
2021-07-10 13:43:53 +02:00
} ,
2025-02-26 23:18:07 +01:00
eventHandlers = testing.globalEventHandlers ,
2024-03-22 19:14:28 -05:00
}