mirror of
https://gitlab.com/xCrystal/pokecrystal-board.git
synced 2024-11-16 11:27:33 -08:00
Implement Cut technique, making cut trees use objects (#34)
This commit is contained in:
parent
16ba25346c
commit
303deca959
@ -198,6 +198,7 @@ DEF SECONDARY_SPRITES_FIRST_TILE EQU $20
|
|||||||
DEF BOARD_MENU_OAM_FIRST_TILE EQU SECONDARY_SPRITES_FIRST_TILE
|
DEF BOARD_MENU_OAM_FIRST_TILE EQU SECONDARY_SPRITES_FIRST_TILE
|
||||||
DEF DIE_ROLL_OAM_FIRST_TILE EQU BOARD_MENU_OAM_FIRST_TILE + 45 ; max(BOARD_MENU_ITEM_SIZE * NUM_BOARD_MENU_ITEMS, DIE_SIZE * 10)
|
DEF DIE_ROLL_OAM_FIRST_TILE EQU BOARD_MENU_OAM_FIRST_TILE + 45 ; max(BOARD_MENU_ITEM_SIZE * NUM_BOARD_MENU_ITEMS, DIE_SIZE * 10)
|
||||||
DEF DIE_NUMBERS_OAM_FIRST_TILE EQU BOARD_MENU_OAM_FIRST_TILE
|
DEF DIE_NUMBERS_OAM_FIRST_TILE EQU BOARD_MENU_OAM_FIRST_TILE
|
||||||
|
DEF CUT_TREE_OAM_FIRST_TILE EQU DIE_NUMBERS_OAM_FIRST_TILE + 40 ; DIE_NUMBER_SIZE * 10
|
||||||
|
|
||||||
; Branch space
|
; Branch space
|
||||||
DEF BRANCH_ARROWS_OAM_FIRST_TILE EQU DIE_NUMBERS_OAM_FIRST_TILE + 40 ; DIE_NUMBER_SIZE * 10
|
DEF BRANCH_ARROWS_OAM_FIRST_TILE EQU DIE_NUMBERS_OAM_FIRST_TILE + 40 ; DIE_NUMBER_SIZE * 10
|
||||||
|
@ -187,6 +187,7 @@ DEF MAPOBJECT_SCREEN_HEIGHT EQU (SCREEN_HEIGHT / 2) + 2
|
|||||||
const SPRITEMOVEDATA_BOULDERDUST ; 22
|
const SPRITEMOVEDATA_BOULDERDUST ; 22
|
||||||
const SPRITEMOVEDATA_GRASS ; 23
|
const SPRITEMOVEDATA_GRASS ; 23
|
||||||
const SPRITEMOVEDATA_SWIM_WANDER ; 24
|
const SPRITEMOVEDATA_SWIM_WANDER ; 24
|
||||||
|
const SPRITEMOVEDATA_CUTTABLE_TREE ; 25
|
||||||
DEF NUM_SPRITEMOVEDATA EQU const_value
|
DEF NUM_SPRITEMOVEDATA EQU const_value
|
||||||
|
|
||||||
; StepFunction_FromMovement.Pointers indexes (see engine/overworld/map_objects.asm)
|
; StepFunction_FromMovement.Pointers indexes (see engine/overworld/map_objects.asm)
|
||||||
|
@ -145,7 +145,7 @@ DEF NUM_BGEVENTS EQU const_value
|
|||||||
const OBJECTTYPE_TRAINER
|
const OBJECTTYPE_TRAINER
|
||||||
const OBJECTTYPE_TALKER
|
const OBJECTTYPE_TALKER
|
||||||
const OBJECTTYPE_ROCK
|
const OBJECTTYPE_ROCK
|
||||||
const OBJECTTYPE_5
|
const OBJECTTYPE_TREE
|
||||||
const OBJECTTYPE_6
|
const OBJECTTYPE_6
|
||||||
DEF NUM_OBJECT_TYPES EQU const_value
|
DEF NUM_OBJECT_TYPES EQU const_value
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@
|
|||||||
const SPRITE_ENTEI ; 64
|
const SPRITE_ENTEI ; 64
|
||||||
const SPRITE_RAIKOU ; 65
|
const SPRITE_RAIKOU ; 65
|
||||||
const SPRITE_STANDING_YOUNGSTER ; 66
|
const SPRITE_STANDING_YOUNGSTER ; 66
|
||||||
|
const SPRITE_CUT_TREE ; 67
|
||||||
DEF NUM_OVERWORLD_SPRITES EQU const_value - 1
|
DEF NUM_OVERWORLD_SPRITES EQU const_value - 1
|
||||||
|
|
||||||
; SpriteMons indexes (see data/sprites/sprite_mons.asm)
|
; SpriteMons indexes (see data/sprites/sprite_mons.asm)
|
||||||
|
@ -29,7 +29,8 @@ Level1GroupSprites:
|
|||||||
db SPRITE_POKE_BALL
|
db SPRITE_POKE_BALL
|
||||||
db SPRITE_FRUIT_TREE
|
db SPRITE_FRUIT_TREE
|
||||||
db SPRITE_ROCK
|
db SPRITE_ROCK
|
||||||
; max 3 of 10 still sprites
|
db SPRITE_CUT_TREE
|
||||||
|
; max 4 of 10 still sprites
|
||||||
db 0 ; end
|
db 0 ; end
|
||||||
|
|
||||||
; Level2GroupSprites:
|
; Level2GroupSprites:
|
||||||
@ -54,6 +55,7 @@ DebugLevel5GroupSprites:
|
|||||||
db SPRITE_POKE_BALL
|
db SPRITE_POKE_BALL
|
||||||
db SPRITE_FRUIT_TREE
|
db SPRITE_FRUIT_TREE
|
||||||
db SPRITE_ROCK
|
db SPRITE_ROCK
|
||||||
; max 3 of 10 still sprites
|
db SPRITE_CUT_TREE
|
||||||
|
; max 4 of 10 still sprites
|
||||||
db 0 ; end
|
db 0 ; end
|
||||||
endc
|
endc
|
||||||
|
@ -298,6 +298,14 @@ SpriteMovementData::
|
|||||||
db 0 ; flags2
|
db 0 ; flags2
|
||||||
db SWIMMING ; palette flags
|
db SWIMMING ; palette flags
|
||||||
|
|
||||||
|
; SPRITEMOVEDATA_CUTTABLE_TREE
|
||||||
|
db SPRITEMOVEFN_STANDING ; movement function
|
||||||
|
db DOWN ; facing
|
||||||
|
db OBJECT_ACTION_STAND ; action
|
||||||
|
db WONT_DELETE | FIXED_FACING | SLIDING | MOVE_ANYWHERE ; flags1
|
||||||
|
db 0 ; flags2
|
||||||
|
db 0 ; palette flags
|
||||||
|
|
||||||
assert_table_length NUM_SPRITEMOVEDATA
|
assert_table_length NUM_SPRITEMOVEDATA
|
||||||
|
|
||||||
; unused
|
; unused
|
||||||
|
@ -109,4 +109,5 @@ OverworldSprites:
|
|||||||
overworld_sprite EnteiSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
|
overworld_sprite EnteiSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
|
||||||
overworld_sprite RaikouSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
|
overworld_sprite RaikouSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
|
||||||
overworld_sprite StandingYoungsterSpriteGFX, 12, STANDING_SPRITE, PAL_OW_BLUE
|
overworld_sprite StandingYoungsterSpriteGFX, 12, STANDING_SPRITE, PAL_OW_BLUE
|
||||||
|
overworld_sprite CutTreeSpriteGFX, 4, STILL_SPRITE, PAL_OW_TREE
|
||||||
assert_table_length NUM_OVERWORLD_SPRITES
|
assert_table_length NUM_OVERWORLD_SPRITES
|
||||||
|
@ -409,6 +409,7 @@ BoardMenu_BreakDieAnimation:
|
|||||||
.done
|
.done
|
||||||
ld hl, wVramState
|
ld hl, wVramState
|
||||||
res 2, [hl]
|
res 2, [hl]
|
||||||
|
farcall ClearSpriteAnims
|
||||||
ld hl, wDisplaySecondarySprites
|
ld hl, wDisplaySecondarySprites
|
||||||
set SECONDARYSPRITES_SPACES_LEFT_F, [hl]
|
set SECONDARYSPRITES_SPACES_LEFT_F, [hl]
|
||||||
ld a, [wDieRoll]
|
ld a, [wDieRoll]
|
||||||
|
@ -109,6 +109,140 @@ TreeRelativeLocationTable:
|
|||||||
dwcoord 8 - 2, 8 ; DOWN
|
dwcoord 8 - 2, 8 ; DOWN
|
||||||
dwcoord 8 + 2, 8 ; UP
|
dwcoord 8 + 2, 8 ; UP
|
||||||
|
|
||||||
|
OWCutAnimation_WithCutTreeAsObject:
|
||||||
|
ld a, $a0
|
||||||
|
ld [wCutTreeOAMAddr], a
|
||||||
|
ld de, CutTreeGFX
|
||||||
|
ld hl, vTiles0 tile CUT_TREE_OAM_FIRST_TILE
|
||||||
|
lb bc, BANK(CutTreeGFX), 4
|
||||||
|
call Request2bpp
|
||||||
|
call WaitSFX
|
||||||
|
ld de, SFX_PLACE_PUZZLE_PIECE_DOWN
|
||||||
|
call PlaySFX
|
||||||
|
xor a
|
||||||
|
ld [wJumptableIndex], a
|
||||||
|
.loop
|
||||||
|
ld a, [wJumptableIndex]
|
||||||
|
bit 7, a
|
||||||
|
jr nz, .finish
|
||||||
|
call .FindCutTreeOAMAddr
|
||||||
|
ld a, 36 * SPRITEOAMSTRUCT_LENGTH
|
||||||
|
jr nc, .got_oam_addr
|
||||||
|
ld a, l
|
||||||
|
.got_oam_addr
|
||||||
|
ld [wCurSpriteOAMAddr], a
|
||||||
|
ld [wCutTreeOAMAddr], a
|
||||||
|
ld hl, wVramState
|
||||||
|
set 2, [hl] ; do not clear wShadowOAM during DoNextFrameForAllSprites
|
||||||
|
callfar DoNextFrameForAllSprites
|
||||||
|
ld hl, wVramState
|
||||||
|
res 2, [hl]
|
||||||
|
call .OWCutJumptable
|
||||||
|
call DelayFrame
|
||||||
|
jr .loop
|
||||||
|
|
||||||
|
.finish
|
||||||
|
farcall ClearSpriteAnims
|
||||||
|
ret
|
||||||
|
|
||||||
|
; find the sprite in wShadowOAM with coordinates that match exactly the tile facing the player.
|
||||||
|
; if found, return in l its location within wShadowOAM and return carry.
|
||||||
|
; if it has already been found during this animation and thus copied into wCutTreeOAMAddr, return that value instead.
|
||||||
|
; otherwise return nc.
|
||||||
|
.FindCutTreeOAMAddr:
|
||||||
|
ld a, [wCutTreeOAMAddr]
|
||||||
|
cp $a0
|
||||||
|
ld l, a
|
||||||
|
scf
|
||||||
|
ret nz ; c
|
||||||
|
call .GetPixelFacing
|
||||||
|
; .GetPixelFacing returns the coordinates of the bottom right object.
|
||||||
|
; convert them to the top left object.
|
||||||
|
ld a, d
|
||||||
|
sub TILE_WIDTH
|
||||||
|
ld d, a
|
||||||
|
ld a, e
|
||||||
|
sub TILE_WIDTH
|
||||||
|
ld e, a
|
||||||
|
ld hl, wShadowOAM
|
||||||
|
ld bc, 4 * SPRITEOAMSTRUCT_LENGTH
|
||||||
|
.sprite_loop
|
||||||
|
ld a, [hl]
|
||||||
|
cp d
|
||||||
|
jr nz, .next_sprite
|
||||||
|
inc hl
|
||||||
|
ld a, [hld]
|
||||||
|
cp e
|
||||||
|
scf
|
||||||
|
ret z ; c
|
||||||
|
.next_sprite
|
||||||
|
add hl, bc
|
||||||
|
ld a, l
|
||||||
|
cp LOW(wShadowOAMEnd)
|
||||||
|
ret nc
|
||||||
|
jr .sprite_loop
|
||||||
|
|
||||||
|
.OWCutJumptable:
|
||||||
|
jumptable .dw, wJumptableIndex
|
||||||
|
|
||||||
|
.dw
|
||||||
|
dw .Cut_SpawnAnimateTree
|
||||||
|
dw .Cut_StartWaiting
|
||||||
|
dw .Cut_WaitAnimSFX
|
||||||
|
|
||||||
|
.Cut_SpawnAnimateTree:
|
||||||
|
call .GetPixelFacing
|
||||||
|
ld a, SPRITE_ANIM_OBJ_CUT_TREE
|
||||||
|
call InitSpriteAnimStruct
|
||||||
|
ld hl, SPRITEANIMSTRUCT_TILE_ID
|
||||||
|
add hl, bc
|
||||||
|
ld [hl], CUT_TREE_OAM_FIRST_TILE
|
||||||
|
ld a, 32
|
||||||
|
ld [wFrameCounter], a
|
||||||
|
; .Cut_StartWaiting
|
||||||
|
ld hl, wJumptableIndex
|
||||||
|
inc [hl]
|
||||||
|
ret
|
||||||
|
|
||||||
|
.GetPixelFacing:
|
||||||
|
ld a, [wPlayerDirection]
|
||||||
|
and %00001100
|
||||||
|
srl a
|
||||||
|
ld e, a
|
||||||
|
ld d, 0
|
||||||
|
ld hl, .Coords
|
||||||
|
add hl, de
|
||||||
|
ld e, [hl]
|
||||||
|
inc hl
|
||||||
|
ld d, [hl]
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Coords:
|
||||||
|
dbpixel 10, 12, 0, 4
|
||||||
|
dbpixel 10, 8, 0, 4
|
||||||
|
dbpixel 8, 10, 0, 4
|
||||||
|
dbpixel 12, 10, 0, 4
|
||||||
|
|
||||||
|
.Cut_StartWaiting:
|
||||||
|
ld a, 1
|
||||||
|
ldh [hBGMapMode], a
|
||||||
|
; .Cut_WaitAnimSFX
|
||||||
|
ld hl, wJumptableIndex
|
||||||
|
inc [hl]
|
||||||
|
|
||||||
|
.Cut_WaitAnimSFX:
|
||||||
|
ld hl, wFrameCounter
|
||||||
|
ld a, [hl]
|
||||||
|
and a
|
||||||
|
jr z, .finished
|
||||||
|
dec [hl]
|
||||||
|
ret
|
||||||
|
|
||||||
|
.finished
|
||||||
|
ld hl, wJumptableIndex
|
||||||
|
set 7, [hl]
|
||||||
|
ret
|
||||||
|
|
||||||
OWCutAnimation:
|
OWCutAnimation:
|
||||||
; Animation index in e
|
; Animation index in e
|
||||||
; 0: Split tree in half
|
; 0: Split tree in half
|
||||||
|
@ -233,6 +233,18 @@ CutDownTreeOrGrass:
|
|||||||
call LoadStandardFont
|
call LoadStandardFont
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
Script_CutAuto::
|
||||||
|
refreshscreen
|
||||||
|
callasm CutDownTreeObject
|
||||||
|
disappear LAST_TALKED
|
||||||
|
special SetObjectToRemainHidden
|
||||||
|
reloadmappart
|
||||||
|
end
|
||||||
|
|
||||||
|
CutDownTreeObject:
|
||||||
|
farcall OWCutAnimation_WithCutTreeAsObject
|
||||||
|
ret
|
||||||
|
|
||||||
CheckOverworldTileArrays:
|
CheckOverworldTileArrays:
|
||||||
; Input: c contains the tile you're facing
|
; Input: c contains the tile you're facing
|
||||||
; Output: Replacement tile in b and effect on wild encounters in c, plus carry set.
|
; Output: Replacement tile in b and effect on wild encounters in c, plus carry set.
|
||||||
@ -1370,7 +1382,7 @@ RockSmashScript:
|
|||||||
end
|
end
|
||||||
|
|
||||||
RockSmashAutoScript::
|
RockSmashAutoScript::
|
||||||
special WaitSFX
|
waitsfx
|
||||||
playsound SFX_STRENGTH
|
playsound SFX_STRENGTH
|
||||||
earthquake 84
|
earthquake 84
|
||||||
applymovementlasttalked MovementData_RockSmash
|
applymovementlasttalked MovementData_RockSmash
|
||||||
|
@ -647,6 +647,8 @@ CheckFacingTileEvent:
|
|||||||
|
|
||||||
call .TryObjectEvent
|
call .TryObjectEvent
|
||||||
jr c, .Action
|
jr c, .Action
|
||||||
|
call .TryTileCollisionEvent
|
||||||
|
jr c, .Action
|
||||||
; fallthrough
|
; fallthrough
|
||||||
|
|
||||||
.NoAction:
|
.NoAction:
|
||||||
@ -704,8 +706,8 @@ CheckFacingTileEvent:
|
|||||||
dbw OBJECTTYPE_ITEMBALL, .none
|
dbw OBJECTTYPE_ITEMBALL, .none
|
||||||
dbw OBJECTTYPE_TRAINER, .none
|
dbw OBJECTTYPE_TRAINER, .none
|
||||||
dbw OBJECTTYPE_TALKER, .none
|
dbw OBJECTTYPE_TALKER, .none
|
||||||
dbw OBJECTTYPE_ROCK, .rock_smash
|
dbw OBJECTTYPE_ROCK, .rock
|
||||||
dbw OBJECTTYPE_5, .none
|
dbw OBJECTTYPE_TREE, .tree
|
||||||
dbw OBJECTTYPE_6, .none
|
dbw OBJECTTYPE_6, .none
|
||||||
assert_table_length NUM_OBJECT_TYPES
|
assert_table_length NUM_OBJECT_TYPES
|
||||||
db -1 ; end
|
db -1 ; end
|
||||||
@ -714,12 +716,22 @@ CheckFacingTileEvent:
|
|||||||
xor a
|
xor a
|
||||||
ret ; nc
|
ret ; nc
|
||||||
|
|
||||||
.rock_smash
|
.rock
|
||||||
ld a, BANK(RockSmashAutoScript)
|
ld a, BANK(RockSmashAutoScript)
|
||||||
ld hl, RockSmashAutoScript
|
ld hl, RockSmashAutoScript
|
||||||
call CallScript
|
call CallScript
|
||||||
ret ; c
|
ret ; c
|
||||||
|
|
||||||
|
.tree
|
||||||
|
ld a, BANK(Script_CutAuto)
|
||||||
|
ld hl, Script_CutAuto
|
||||||
|
call CallScript
|
||||||
|
ret ; c
|
||||||
|
|
||||||
|
.TryTileCollisionEvent:
|
||||||
|
xor a
|
||||||
|
ret ; nc
|
||||||
|
|
||||||
RunSceneScript:
|
RunSceneScript:
|
||||||
ldh a, [hCurBoardEvent]
|
ldh a, [hCurBoardEvent]
|
||||||
cp BOARDEVENT_VIEW_MAP_MODE
|
cp BOARDEVENT_VIEW_MAP_MODE
|
||||||
@ -908,7 +920,7 @@ ObjectEventTypeArray:
|
|||||||
; the remaining four are dummy events
|
; the remaining four are dummy events
|
||||||
dbw OBJECTTYPE_TALKER, .three
|
dbw OBJECTTYPE_TALKER, .three
|
||||||
dbw OBJECTTYPE_ROCK, .four
|
dbw OBJECTTYPE_ROCK, .four
|
||||||
dbw OBJECTTYPE_5, .five
|
dbw OBJECTTYPE_TREE, .five
|
||||||
dbw OBJECTTYPE_6, .six
|
dbw OBJECTTYPE_6, .six
|
||||||
assert_table_length NUM_OBJECT_TYPES
|
assert_table_length NUM_OBJECT_TYPES
|
||||||
db -1 ; end
|
db -1 ; end
|
||||||
@ -1456,7 +1468,7 @@ TryTileCollisionEvent::
|
|||||||
|
|
||||||
.done
|
.done
|
||||||
call PlayClickSFX
|
call PlayClickSFX
|
||||||
ld a, $ff
|
ld a, PLAYEREVENT_MAPSCRIPT
|
||||||
scf
|
scf
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
@ -106,3 +106,4 @@ SuicuneSpriteGFX:: INCBIN "gfx/sprites/suicune.2bpp"
|
|||||||
EnteiSpriteGFX:: INCBIN "gfx/sprites/entei.2bpp"
|
EnteiSpriteGFX:: INCBIN "gfx/sprites/entei.2bpp"
|
||||||
RaikouSpriteGFX:: INCBIN "gfx/sprites/raikou.2bpp"
|
RaikouSpriteGFX:: INCBIN "gfx/sprites/raikou.2bpp"
|
||||||
StandingYoungsterSpriteGFX:: INCBIN "gfx/sprites/standing_youngster.2bpp"
|
StandingYoungsterSpriteGFX:: INCBIN "gfx/sprites/standing_youngster.2bpp"
|
||||||
|
CutTreeSpriteGFX:: INCBIN "gfx/sprites/cut_tree.2bpp"
|
||||||
|
BIN
gfx/sprites/cut_tree.png
Executable file
BIN
gfx/sprites/cut_tree.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 293 B |
@ -21,6 +21,8 @@ DebugLevel5_Map1_MapEvents:
|
|||||||
def_object_events
|
def_object_events
|
||||||
object_event 10, 2, SPRITE_YOUNGSTER, SPRITEMOVEDATA_STANDING_DOWN, 0, 0, -1, -1, PAL_NPC_BLUE, OBJECTTYPE_TRAINER, 2, .DebugLevel5_Map1TrainerYoungsterMikey1, -1
|
object_event 10, 2, SPRITE_YOUNGSTER, SPRITEMOVEDATA_STANDING_DOWN, 0, 0, -1, -1, PAL_NPC_BLUE, OBJECTTYPE_TRAINER, 2, .DebugLevel5_Map1TrainerYoungsterMikey1, -1
|
||||||
object_event 9, 2, SPRITE_YOUNGSTER, SPRITEMOVEDATA_STANDING_DOWN, 0, 0, -1, -1, PAL_NPC_BLUE, OBJECTTYPE_TALKER, 2, .DebugLevel5_Map1Talker1, -1
|
object_event 9, 2, SPRITE_YOUNGSTER, SPRITEMOVEDATA_STANDING_DOWN, 0, 0, -1, -1, PAL_NPC_BLUE, OBJECTTYPE_TALKER, 2, .DebugLevel5_Map1Talker1, -1
|
||||||
|
object_event 6, 1, SPRITE_CUT_TREE, SPRITEMOVEDATA_CUTTABLE_TREE, 0, 0, -1, -1, PAL_NPC_TREE, OBJECTTYPE_TREE, 0, ObjectEvent, -1
|
||||||
|
object_event 9, 4, SPRITE_CUT_TREE, SPRITEMOVEDATA_CUTTABLE_TREE, 0, 0, -1, -1, PAL_NPC_TREE, OBJECTTYPE_TREE, 0, ObjectEvent, -1
|
||||||
|
|
||||||
.DebugLevel5_Map1TrainerYoungsterMikey1:
|
.DebugLevel5_Map1TrainerYoungsterMikey1:
|
||||||
trainer YOUNGSTER, MIKEY, EVENT_LEVEL_SCOPED_1, .YoungsterMikeySeenText, .YoungsterMikeyBeatenText, 0, .Script
|
trainer YOUNGSTER, MIKEY, EVENT_LEVEL_SCOPED_1, .YoungsterMikeySeenText, .YoungsterMikeyBeatenText, 0, .Script
|
||||||
|
@ -205,6 +205,7 @@ wSpriteAnimationStructsEnd::
|
|||||||
wSpriteAnimCount:: db
|
wSpriteAnimCount:: db
|
||||||
wCurSpriteOAMAddr:: db
|
wCurSpriteOAMAddr:: db
|
||||||
|
|
||||||
|
wCutTreeOAMAddr::
|
||||||
wCurIcon:: db
|
wCurIcon:: db
|
||||||
|
|
||||||
wCurIconTile:: db
|
wCurIconTile:: db
|
||||||
|
Loading…
Reference in New Issue
Block a user