Shop update 2/5
parent
cc83ae6e10
commit
f808b0452f
53
arena.lua
53
arena.lua
|
@ -14,6 +14,10 @@ function Arena:on_enter(from, level, units, passives)
|
|||
self.units = units
|
||||
self.passives = passives
|
||||
|
||||
if not state.mouse_control then
|
||||
input:set_mouse_visible(false)
|
||||
end
|
||||
|
||||
trigger:tween(2, main_song_instance, {volume = 0.5, pitch = 1}, math.linear)
|
||||
|
||||
steam.friends.setRichPresence('steam_display', '#StatusFull')
|
||||
|
@ -332,6 +336,7 @@ function Arena:update(dt)
|
|||
|
||||
if input.escape.pressed and not self.transitioning and not self.in_credits and not self.choosing_passives then
|
||||
if not self.paused then
|
||||
input:set_mouse_visible(true)
|
||||
trigger:tween(0.25, _G, {slow_amount = 0}, math.linear, function()
|
||||
slow_amount = 0
|
||||
self.paused = true
|
||||
|
@ -351,6 +356,7 @@ function Arena:update(dt)
|
|||
self.ng_t = nil
|
||||
if self.resume_button then self.resume_button.dead = true; self.resume_button = nil end
|
||||
if self.restart_button then self.restart_button.dead = true; self.restart_button = nil end
|
||||
if self.mouse_button then self.mouse_button.dead = true; self.mouse_button = nil end
|
||||
if self.sfx_button then self.sfx_button.dead = true; self.sfx_button = nil end
|
||||
if self.music_button then self.music_button.dead = true; self.music_button = nil end
|
||||
if self.video_button_1 then self.video_button_1.dead = true; self.video_button_1 = nil end
|
||||
|
@ -392,28 +398,44 @@ function Arena:update(dt)
|
|||
end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)}
|
||||
end}
|
||||
|
||||
self.sfx_button = Button{group = self.ui, x = gw/2, y = gh - 175, force_update = true, button_text = 'toggle sfx (n)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
|
||||
self.mouse_button = Button{group = self.ui, x = gw/2, y = gh - 150, force_update = true, button_text = 'mouse control: ' .. tostring(state.mouse_control and 'yes' or 'no'), fg_color = 'bg10', bg_color = 'bg',
|
||||
action = function(b)
|
||||
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
state.mouse_control = not state.mouse_control
|
||||
b:set_text('mouse control: ' .. tostring(state.mouse_control and 'yes' or 'no'))
|
||||
input:set_mouse_visible(state.mouse_control)
|
||||
end}
|
||||
|
||||
self.sfx_button = Button{group = self.ui, x = gw/2 - 46, y = gh - 175, force_update = true, button_text = 'sounds (n): ' .. tostring(state.volume_muted and 'no' or 'yes'), fg_color = 'bg10', bg_color = 'bg',
|
||||
action = function(b)
|
||||
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
b.spring:pull(0.2, 200, 10)
|
||||
b.selected = true
|
||||
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
if sfx.volume == 0.5 then
|
||||
sfx.volume = 0
|
||||
state.volume_muted = true
|
||||
elseif sfx.volume == 0 then
|
||||
sfx.volume = 0.5
|
||||
state.volume_muted = false
|
||||
end
|
||||
b:set_text('sounds (n): ' .. tostring(state.volume_muted and 'no' or 'yes'))
|
||||
end}
|
||||
|
||||
self.music_button = Button{group = self.ui, x = gw/2, y = gh - 150, force_update = true, button_text = 'toggle music (m)', fg_color = 'bg10', bg_color = 'bg', action = function(b)
|
||||
self.music_button = Button{group = self.ui, x = gw/2 + 46, y = gh - 175, force_update = true, button_text = 'music (m): ' .. tostring(state.music_muted and 'no' or 'yes'), fg_color = 'bg10', bg_color = 'bg',
|
||||
action = function(b)
|
||||
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
b.spring:pull(0.2, 200, 10)
|
||||
b.selected = true
|
||||
ui_switch1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
if music.volume == 0.5 then
|
||||
music.volume = 0
|
||||
state.music_muted = true
|
||||
elseif music.volume == 0 then
|
||||
music.volume = 0.5
|
||||
state.music_muted = false
|
||||
end
|
||||
b:set_text('music (m): ' .. tostring(state.music_muted and 'no' or 'yes'))
|
||||
end}
|
||||
|
||||
self.video_button_1 = Button{group = self.ui, x = gw/2 - 86, y = gh - 125, force_update = true, button_text = 'window size-', fg_color = 'bg10', bg_color = 'bg', action = function()
|
||||
|
@ -499,6 +521,9 @@ function Arena:update(dt)
|
|||
end}
|
||||
end, 'pause')
|
||||
else
|
||||
if not state.mouse_control then
|
||||
input:set_mouse_visible(false)
|
||||
end
|
||||
trigger:tween(0.25, _G, {slow_amount = 1}, math.linear, function()
|
||||
slow_amount = 1
|
||||
self.paused = false
|
||||
|
@ -510,6 +535,7 @@ function Arena:update(dt)
|
|||
self.ng_t = nil
|
||||
if self.resume_button then self.resume_button.dead = true; self.resume_button = nil end
|
||||
if self.restart_button then self.restart_button.dead = true; self.restart_button = nil end
|
||||
if self.mouse_button then self.mouse_button.dead = true; self.mouse_button = nil end
|
||||
if self.sfx_button then self.sfx_button.dead = true; self.sfx_button = nil end
|
||||
if self.music_button then self.music_button.dead = true; self.music_button = nil end
|
||||
if self.video_button_1 then self.video_button_1.dead = true; self.video_button_1 = nil end
|
||||
|
@ -807,6 +833,7 @@ function Arena:quit()
|
|||
|
||||
else
|
||||
if not self.arena_clear_text then self.arena_clear_text = Text2{group = self.ui, x = gw/2, y = gh/2 - 48, lines = {{text = '[wavy_mid, cbyc]arena clear!', font = fat_font, alignment = 'center'}}} end
|
||||
self:gain_gold()
|
||||
self.t:after(3, function()
|
||||
if self.level % 3 == 0 then
|
||||
self.arena_clear_text.dead = true
|
||||
|
@ -1030,11 +1057,15 @@ function Arena:create_credits()
|
|||
end
|
||||
|
||||
|
||||
function Arena:gain_gold()
|
||||
self.gold_gained = random:int(level_to_gold_gained[self.level][1], level_to_gold_gained[self.level][2])
|
||||
self.interest = math.min(math.floor(gold/5), 5)
|
||||
gold = gold + self.gold_gained + self.interest
|
||||
end
|
||||
|
||||
|
||||
function Arena:transition()
|
||||
self.transitioning = true
|
||||
local gold_gained = random:int(level_to_gold_gained[self.level][1], level_to_gold_gained[self.level][2])
|
||||
local interest = math.min(math.floor(gold/5), 5)
|
||||
gold = gold + gold_gained + interest
|
||||
ui_transition2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
TransitionEffect{group = main.transitions, x = self.player.x, y = self.player.y, color = self.color, transition_action = function(t)
|
||||
slow_amount = 1
|
||||
|
@ -1043,23 +1074,23 @@ function Arena:transition()
|
|||
main:go_to('buy_screen', self.level, self.units, self.passives)
|
||||
t.t:after(0.1, function()
|
||||
t.text:set_text({
|
||||
{text = '[nudge_down, bg]gold gained: ' .. tostring(gold_gained), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[nudge_down, bg]gold gained: ' .. tostring(self.gold_gained or 0), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[wavy_lower, bg]interest: 0', font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[wavy_lower, bg]total: 0', font = pixul_font, alignment = 'center'}
|
||||
})
|
||||
_G[random:table{'coins1', 'coins2', 'coins3'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
t.t:after(0.2, function()
|
||||
t.text:set_text({
|
||||
{text = '[wavy_lower, bg]gold gained: ' .. tostring(gold_gained), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[nudge_down, bg]interest: ' .. tostring(interest), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[wavy_lower, bg]gold gained: ' .. tostring(self.gold_gained or 0), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[nudge_down, bg]interest: ' .. tostring(self.interest or 0), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[wavy_lower, bg]total: 0', font = pixul_font, alignment = 'center'}
|
||||
})
|
||||
_G[random:table{'coins1', 'coins2', 'coins3'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
t.t:after(0.2, function()
|
||||
t.text:set_text({
|
||||
{text = '[wavy_lower, bg]gold gained: ' .. tostring(gold_gained), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[wavy_lower, bg]interest: ' .. tostring(interest), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[nudge_down, bg]total: ' .. tostring(gold_gained + interest), font = pixul_font, alignment = 'center'}
|
||||
{text = '[wavy_lower, bg]gold gained: ' .. tostring(self.gold_gained or 0), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[wavy_lower, bg]interest: ' .. tostring(self.interest or 0), font = pixul_font, alignment = 'center', height_multiplier = 1.5},
|
||||
{text = '[nudge_down, bg]total: ' .. tostring((self.gold_gained or 0) + (self.interest or 0)), font = pixul_font, alignment = 'center'}
|
||||
})
|
||||
_G[random:table{'coins1', 'coins2', 'coins3'}]:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
end)
|
||||
|
|
|
@ -43,6 +43,8 @@ function BuyScreen:on_enter(from, level, units, passives)
|
|||
self.passives = passives
|
||||
camera.x, camera.y = gw/2, gh/2
|
||||
|
||||
input:set_mouse_visible(true)
|
||||
|
||||
if self.level == 0 then
|
||||
main_song_instance = _G[random:table{'song1', 'song2', 'song3', 'song4', 'song5'}]:play{volume = 0.5}
|
||||
self.level = 1
|
||||
|
|
|
@ -637,6 +637,7 @@ function EnemyCritter:update(dt)
|
|||
if self.slowed then self.slow_mvspd_m = self.slowed
|
||||
else self.slow_mvspd_m = 1 end
|
||||
self.buff_mvspd_m = (self.speed_boosting_mvspd_m or 1)*(self.slow_mvspd_m or 1)*(self.temporal_chains_mvspd_m or 1)
|
||||
if not self.classes then return end
|
||||
self:calculate_stats()
|
||||
|
||||
if self.being_pushed then
|
||||
|
|
27
main.lua
27
main.lua
|
@ -9,6 +9,7 @@ require 'media'
|
|||
|
||||
|
||||
function init()
|
||||
print('Initializing engine...')
|
||||
shared_init()
|
||||
|
||||
input:bind('move_left', {'a', 'left', 'dpleft', 'm1'})
|
||||
|
@ -17,6 +18,7 @@ function init()
|
|||
input:bind('move_down', {'s', 'down', 'dpdown'})
|
||||
input:bind('enter', {'space', 'return', 'fleft', 'fdown', 'fright'})
|
||||
|
||||
print('Loading sounds...')
|
||||
local s = {tags = {sfx}}
|
||||
psychic1 = Sound('Magical Impact 13.ogg', s)
|
||||
fire1 = Sound('Fire bolt 3.ogg', s)
|
||||
|
@ -109,6 +111,7 @@ function init()
|
|||
rogue_crit1 = Sound('Dagger Stab (Flesh) 4.ogg', s)
|
||||
rogue_crit2 = Sound('Sword hits another sword 6.ogg', s)
|
||||
|
||||
print('Loading songs...')
|
||||
song1 = Sound('Kubbi - Ember - 01 Pathfinder.ogg', {tags = {music}})
|
||||
song2 = Sound('Kubbi - Ember - 02 Ember.ogg', {tags = {music}})
|
||||
song3 = Sound('Kubbi - Ember - 03 Firelight.ogg', {tags = {music}})
|
||||
|
@ -116,6 +119,7 @@ function init()
|
|||
song5 = Sound('Kubbi - Ember - 05 Compass.ogg', {tags = {music}})
|
||||
death_song = Sound('Kubbi - Ember - 09 Formed by Glaciers.ogg', {tags = {music}})
|
||||
|
||||
print('Loading images...')
|
||||
lock_image = Image('lock')
|
||||
speed_booster_elite = Image('speed_booster_elite')
|
||||
exploder_elite = Image('exploder_elite')
|
||||
|
@ -181,6 +185,7 @@ function init()
|
|||
star = Image('star')
|
||||
arrow = Image('arrow')
|
||||
|
||||
print('Initializing game...')
|
||||
class_colors = {
|
||||
['warrior'] = yellow[0],
|
||||
['ranger'] = green[0],
|
||||
|
@ -1309,7 +1314,7 @@ function init()
|
|||
if not state.new_game_plus then state.new_game_plus = new_game_plus end
|
||||
current_new_game_plus = state.current_new_game_plus or new_game_plus
|
||||
if not state.current_new_game_plus then state.current_new_game_plus = current_new_game_plus end
|
||||
max_units = 7 + new_game_plus
|
||||
max_units = 7 + current_new_game_plus
|
||||
|
||||
main = Main()
|
||||
|
||||
|
@ -1325,15 +1330,15 @@ function init()
|
|||
|
||||
--[[
|
||||
main:add(Arena'arena')
|
||||
main:go_to('arena', 21, {
|
||||
{character = 'arcanist', level = 3},
|
||||
main:go_to('arena', 17, {
|
||||
{character = 'arcanist', level = 2},
|
||||
{character = 'silencer', level = 2},
|
||||
{character = 'warden', level = 2},
|
||||
{character = 'warden', level = 3},
|
||||
{character = 'chronomancer', level = 1},
|
||||
{character = 'witch', level = 2},
|
||||
{character = 'witch', level = 3},
|
||||
{character = 'illusionist', level = 3},
|
||||
{character = 'psychic', level = 2},
|
||||
{character = 'vulcanist', level = 2},
|
||||
{character = 'vulcanist', level = 3},
|
||||
}, passives)
|
||||
]]--
|
||||
|
||||
|
@ -1380,6 +1385,10 @@ function update(dt)
|
|||
]]--
|
||||
|
||||
if input.n.pressed then
|
||||
if main.current.sfx_button then
|
||||
main.current.sfx_button:action()
|
||||
main.current.sfx_button.selected = false
|
||||
else
|
||||
if sfx.volume == 0.5 then
|
||||
sfx.volume = 0
|
||||
state.volume_muted = true
|
||||
|
@ -1388,8 +1397,13 @@ function update(dt)
|
|||
state.volume_muted = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if input.m.pressed then
|
||||
if main.current.music_button then
|
||||
main.current.music_button:action()
|
||||
main.current.music_button.selected = false
|
||||
else
|
||||
if music.volume == 0.5 then
|
||||
state.music_muted = true
|
||||
music.volume = 0
|
||||
|
@ -1398,6 +1412,7 @@ function update(dt)
|
|||
state.music_muted = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if input.k.pressed then
|
||||
if sx > 1 and sy > 1 then
|
||||
|
|
|
@ -237,6 +237,7 @@ function Unit:calculate_stats(first_run)
|
|||
self.base_dmg = (12 + current_new_game_plus*2) + (2 + current_new_game_plus)*y[x]
|
||||
self.base_mvspd = 35 + 1.5*y[x]
|
||||
if x == 25 then
|
||||
self.base_dmg = (12 + current_new_game_plus*2) + (1.25 + current_new_game_plus)*y[x]
|
||||
self.base_mvspd = 35 + 1.2*y[x]
|
||||
end
|
||||
else
|
||||
|
@ -254,6 +255,7 @@ function Unit:calculate_stats(first_run)
|
|||
self.base_dmg = (12 + current_new_game_plus*2) + (2 + current_new_game_plus)*y[x]
|
||||
self.base_mvspd = 35 + 1.5*y[x]
|
||||
if x == 25 then
|
||||
self.base_dmg = (12 + current_new_game_plus*2) + (2 + 0.5*current_new_game_plus)*y[x]
|
||||
self.base_mvspd = 35 + 1.2*y[x]
|
||||
end
|
||||
else
|
||||
|
|
35
player.lua
35
player.lua
|
@ -261,7 +261,14 @@ function Player:init(args)
|
|||
if x == 0 and y == 0 then x, y = gw/2, gh/2 end
|
||||
x, y = x + self.x, y + self.y
|
||||
x, y = x/2, y/2
|
||||
trigger:every_immediate(0.1, function()
|
||||
local check_circle = Circle(x, y, 2)
|
||||
local objects = main.current.main:get_objects_in_shape(check_circle, {Player, Seeker, EnemyCritter, Critter, Illusion, Saboteur, Pet, Turret})
|
||||
if #objects == 0 then
|
||||
Volcano{group = main.current.main, x = x, y = y, color = self.color, parent = self, rs = 24, level = self.level}
|
||||
trigger:cancel('volcano_spawn')
|
||||
end
|
||||
end, nil, nil, 'volcano_spawn')
|
||||
end
|
||||
volcano()
|
||||
if main.current.sorcerer_level > 0 then
|
||||
|
@ -596,17 +603,39 @@ function Player:init(args)
|
|||
local unit_2 = random:table_remove(units)
|
||||
if unit_1 then
|
||||
illusion1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
trigger:every_immediate(0.1, function()
|
||||
local check_circle = Circle(unit_1.x, unit_1.y, 6)
|
||||
local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter})
|
||||
if #objects == 0 then
|
||||
ForceField{group = main.current.main, x = unit_1.x, y = unit_1.y, parent = unit_1}
|
||||
trigger:cancel('warden_force_field_1')
|
||||
end
|
||||
end, nil, nil, 'warden_force_field_1')
|
||||
end
|
||||
if unit_2 then
|
||||
illusion1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
ForceField{group = main.current.main, x = unit_2.x, y = unit_2.y, parent = unit_2}
|
||||
trigger:every_immediate(0.1, function()
|
||||
local check_circle = Circle(unit_2.x, unit_2.y, 6)
|
||||
local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter})
|
||||
if #objects == 0 then
|
||||
ForceField{group = main.current.main, x = unit_2.x, y = unit_2.y, parent = unit_2}
|
||||
trigger:cancel('warden_force_field_2')
|
||||
end
|
||||
end, nil, nil, 'warden_force_field_2')
|
||||
end
|
||||
else
|
||||
local unit = random:table(self:get_all_units())
|
||||
if unit then
|
||||
illusion1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
|
||||
trigger:every_immediate(0.1, function()
|
||||
local check_circle = Circle(unit.x, unit.y, 6)
|
||||
local objects = main.current.main:get_objects_in_shape(check_circle, {Seeker, EnemyCritter})
|
||||
if #objects == 0 then
|
||||
ForceField{group = main.current.main, x = unit.x, y = unit.y, parent = unit}
|
||||
trigger:cancel('warden_force_field_0')
|
||||
end
|
||||
end, nil, nil, 'warden_force_field_0')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1022,6 +1051,11 @@ function Player:update(dt)
|
|||
if input.move_left.down then self.r = self.r - 1.66*math.pi*dt end
|
||||
if input.move_right.down then self.r = self.r + 1.66*math.pi*dt end
|
||||
|
||||
if state.mouse_control then
|
||||
local v = Vector(math.cos(self.r), math.sin(self.r)):perpendicular():dot(Vector(math.cos(self:angle_to_mouse()), math.sin(self:angle_to_mouse())))
|
||||
self.r = self.r + math.sign(v)*1.66*math.pi*dt
|
||||
end
|
||||
|
||||
local total_v = 0
|
||||
local units = self:get_all_units()
|
||||
for _, unit in ipairs(units) do
|
||||
|
@ -2522,6 +2556,7 @@ Pet:implement(GameObject)
|
|||
Pet:implement(Physics)
|
||||
function Pet:init(args)
|
||||
self:init_game_object(args)
|
||||
if tostring(self.x) == tostring(0/0) or tostring(self.y) == tostring(0/0) then self.dead = true; return end
|
||||
self:set_as_rectangle(8, 8, 'dynamic', 'projectile')
|
||||
self:set_restitution(0.5)
|
||||
self.hfx:add('hit', 1)
|
||||
|
|
24
todo
24
todo
|
@ -16,18 +16,17 @@ Shop Update
|
|||
Owned units highlighted in shop
|
||||
Fix highlight colors and highlight reserve
|
||||
Shop level up
|
||||
Remove level 3 units from rotation
|
||||
|
||||
Item changes
|
||||
|
||||
QoL
|
||||
* Added sorcerer achievement
|
||||
* Removed restart button from end screen
|
||||
* First item reroll is now free
|
||||
Gold is given right after the round ends in the arena rather than on the shop for easier item rerolls
|
||||
Show hero HP
|
||||
Endless mode
|
||||
Remove level 3 units from rotation
|
||||
Hide cursor during waves
|
||||
Mouse follow control
|
||||
Volume slider
|
||||
* Gold is given right after the round ends in the arena rather than on the shop for easier item rerolls
|
||||
* Added mouse follow control mode
|
||||
* The cursor is now invisible during waves, unless mouse follow mode is enabled
|
||||
Options menu from buy screen
|
||||
Add visuals for defensive ouroboros, divine intervention, fairy buff
|
||||
|
||||
|
@ -37,13 +36,15 @@ Shop Update
|
|||
Bug fixes
|
||||
* Fixed a crash when too many illusions would be spawned in short succession
|
||||
* Fixed a bug where enemy critters would sometimes be unkillable
|
||||
* Fixed a rare crash involving broken enemy critter state
|
||||
* Fixed text for the illusionist's Lv.3 effect going outside the screen
|
||||
* Fixed a rare crash when hovering over your owned in the shop
|
||||
Fix a crash when warden's force field would spawn on top of enemies
|
||||
Fix enemies still spawning after arena clear (this happens with the extra enemy spawns that were blocked earlier)
|
||||
* Fixed a crash when warden's force field would spawn on top of enemies
|
||||
* Fixed a crash when a pet would spawn on top of enemies
|
||||
* Fixed a bug where the maximum number of units would be wrong on certain conditions
|
||||
Fix bug where quitting on level 2 arena goes back to level 1 shop
|
||||
Fix fullscreen with different resolutions that don't scale properly
|
||||
https://i.imgur.com/Lxu8skX.png
|
||||
https://i.imgur.com/mnivI4d.png
|
||||
Fix enemies still spawning after arena clear (this happens with the extra enemy spawns that were blocked earlier)
|
||||
|
||||
Sacrifice Update
|
||||
New mechanics
|
||||
|
@ -64,6 +65,7 @@ Sacrifice Update
|
|||
Zombie
|
||||
QoL
|
||||
Current items visible on item selection screen
|
||||
Endless mode
|
||||
|
||||
|
||||
---
|
||||
|
|
Loading…
Reference in New Issue