Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F625155
AttitudesTab.as
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
14 KB
Referenced Files
None
Subscribers
None
AttitudesTab.as
View Options
import
tabs.Tab
;
import
elements.GuiButton
;
import
elements.GuiProgressbar
;
import
elements.GuiPanel
;
import
elements.GuiMarkupText
;
import
elements.GuiEmpire
;
import
elements.GuiOverlay
;
import
elements.GuiSkinElement
;
import
elements.MarkupTooltip
;
import
dialogs.QuestionDialog
;
import
attitudes
;
import
abilities
;
from
overlays
.
InfoBar
import
AbilityAction
;
import
tabs.tabbar
;
class
LevelMarker
:
BaseGuiElement
{
const
AttitudeLevel
@
lvl
;
Color
color
;
bool
reached
;
bool
hovered
=
false
;
LevelMarker
(
IGuiElement
@
parent
)
{
super
(
parent
,
recti
(
0
,
0
,
42
,
70
));
noClip
=
true
;
auto
@
tt
=
addLazyMarkupTooltip
(
this
,
width
=
300
);
tt
.
FollowMouse
=
false
;
tt
.
offset
=
vec2i
(
0
,
5
);
}
string
get_tooltip
()
{
string
tt
;
tt
+=
format
(
"[font=Medium]$1 $2[/font]\n"
,
locale
::
LEVEL
,
toString
(
lvl
.
level
+
1
));
tt
+=
lvl
.
description
;
return
tt
;
}
void
update
(
Attitude
&
att
)
{
double
finalProgress
=
att
.
levels
[
att
.
maxLevel
].
threshold
;
double
pct
=
clamp
(
lvl
.
threshold
/
finalProgress
,
0.0
,
1.0
);
reached
=
att
.
level
>=
lvl
.
level
+
1
;
position
=
vec2i
(
parent
.
size
.
x
*
pct
-
size
.
width
/
2
,
0
);
if
(
reached
)
color
=
Color
(
0x000000ff
).
interpolate
(
att
.
type
.
color
,
0.4
);
else
color
=
Color
(
0x666666ff
);
}
bool
onGuiEvent
(
const
GuiEvent
&
evt
)
{
switch
(
evt
.
type
)
{
case
GUI_Mouse_Entered
:
if
(
evt
.
caller
is
this
)
hovered
=
true
;
break
;
case
GUI_Mouse_Left
:
if
(
evt
.
caller
is
this
)
hovered
=
false
;
break
;
}
return
BaseGuiElement
::
onGuiEvent
(
evt
);
}
void
draw
()
override
{
if
(
hovered
)
{
drawLine
(
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/
2
,
2
),
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/
2
,
size
.
height
-
35
),
Color
(
0xffffff40
),
12
);
}
drawLine
(
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/
2
,
2
),
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/
2
,
size
.
height
),
Color
(
0x00000080
),
5
);
drawLine
(
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/
2
,
2
),
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/
2
,
size
.
height
),
color
,
3
);
recti
iconPos
=
recti_area
(
5
,
size
.
height
-
32
,
32
,
32
)
+
AbsolutePosition
.
topLeft
;
if
(
hovered
)
drawRectangle
(
iconPos
.
padded
(-
4
),
Color
(
0xffffff40
));
drawRectangle
(
iconPos
.
padded
(-
2
),
Color
(
0x00000080
));
drawRectangle
(
iconPos
,
color
);
lvl
.
icon
.
draw
(
iconPos
.
aspectAligned
(
lvl
.
icon
.
aspect
));
BaseGuiElement
::
draw
();
}
};
class
DiscardConfirm
:
QuestionDialogCallback
{
const
AttitudeType
@
type
;
DiscardConfirm
(
const
AttitudeType
@
type
)
{
@
this
.
type
=
type
;
}
void
questionCallback
(
QuestionDialog
@
dialog
,
int
answer
)
{
if
(
answer
==
QA_Yes
)
playerEmpire
.
discardAttitude
(
type
.
id
);
}
};
class
AttitudeBox
:
BaseGuiElement
{
Attitude
@
att
;
GuiMarkupText
@
title
;
GuiMarkupText
@
progressText
;
GuiProgressbar
@
bar
;
array
<
LevelMarker
@
>
markers
;
array
<
AbilityAction
@
>
abilities
;
GuiButton
@
discardButton
;
GuiMarkupText
@
discardText
;
AttitudeBox
(
IGuiElement
@
parent
)
{
super
(
parent
,
recti
());
@
title
=
GuiMarkupText
(
this
,
Alignment
(
Left
+
12
,
Top
+
8
,
Right
-
12
,
Top
+
40
));
title
.
defaultFont
=
FT_Medium
;
title
.
defaultStroke
=
colors
::
Black
;
@
progressText
=
GuiMarkupText
(
this
,
Alignment
(
Left
+
20
,
Top
+
36
,
Right
-
12
,
Top
+
65
));
progressText
.
defaultColor
=
Color
(
0x888888ff
);
progressText
.
defaultStroke
=
colors
::
Black
;
@
bar
=
GuiProgressbar
(
this
,
Alignment
(
Left
+
12
,
Top
+
65
,
Right
-
220
,
Top
+
110
));
@
discardButton
=
GuiButton
(
this
,
Alignment
(
Right
-
140
,
Top
+
3
,
Right
-
4
,
Top
+
36
));
discardButton
.
color
=
colors
::
Red
;
@
discardText
=
GuiMarkupText
(
discardButton
,
Alignment
(
Left
,
Top
+
6
,
Right
,
Bottom
));
}
bool
onGuiEvent
(
const
GuiEvent
&
event
)
override
{
if
(
event
.
type
==
GUI_Clicked
)
{
if
(
event
.
caller
is
discardButton
)
{
auto
@
diag
=
question
(
format
(
locale
::
ATT_CONFIRM_DISCARD
,
att
.
type
.
name
),
locale
::
DISCARD
,
locale
::
CANCEL
,
DiscardConfirm
(
att
.
type
));
diag
.
titleBox
.
color
=
Color
(
0xff0000ff
);
return
true
;
}
}
return
BaseGuiElement
::
onGuiEvent
(
event
);
}
void
update
()
{
if
(
att
is
null
||
att
.
type
is
null
)
return
;
updateAbsolutePosition
();
double
curProgress
=
att
.
progress
;
double
nextProgress
=
att
.
levels
[
att
.
nextLevel
].
threshold
;
double
finalProgress
=
att
.
levels
[
att
.
maxLevel
].
threshold
;
//Progress data
if
(
nextProgress
>
curProgress
)
{
progressText
.
text
=
format
(
"[color=#aaa][b]$1 $2:[/b][/color] $3"
,
locale
::
LEVEL
,
toString
(
att
.
nextLevel
),
format
(
att
.
type
.
progress
,
toString
(
nextProgress
-
curProgress
,
0
)));
}
else
{
progressText
.
text
=
locale
::
ATT_MAX_LEVEL
;
}
title
.
text
=
att
.
type
.
name
;
bar
.
frontColor
=
att
.
type
.
color
;
bar
.
progress
=
curProgress
/
finalProgress
;
//Level markers
uint
prevCnt
=
markers
.
length
;
uint
newCnt
=
att
.
type
.
levels
.
length
;
for
(
uint
i
=
newCnt
;
i
<
prevCnt
;
++
i
)
markers
[
i
].
remove
();
markers
.
length
=
newCnt
;
for
(
uint
i
=
prevCnt
;
i
<
newCnt
;
++
i
)
@
markers
[
i
]
=
LevelMarker
(
bar
);
for
(
uint
i
=
0
;
i
<
newCnt
;
++
i
)
{
@
markers
[
i
].
lvl
=
att
.
type
.
levels
[
i
];
markers
[
i
].
update
(
att
);
}
//Discarding
int
discardCost
=
att
.
getDiscardCost
(
playerEmpire
);
discardText
.
text
=
"[center]"
+
format
(
locale
::
ATT_DISCARD
,
toString
(
discardCost
))+
"[/center]"
;
discardButton
.
disabled
=
playerEmpire
.
Influence
<
discardCost
;
//Abilities
prevCnt
=
abilities
.
length
;
newCnt
=
0
;
for
(
uint
i
=
0
,
cnt
=
att
.
allHookCount
;
i
<
cnt
;
++
i
)
{
Ability
@
abl
;
if
(
newCnt
<
prevCnt
)
@
abl
=
abilities
[
newCnt
].
abl
;
@
abl
=
att
.
allHooks
[
i
].
showAbility
(
att
,
playerEmpire
,
abl
);
if
(
abl
!
is
null
)
{
if
(
newCnt
>=
prevCnt
)
{
AbilityAction
act
(
abl
,
abl
.
type
.
name
);
act
.
independent
=
true
;
@
act
.
parent
=
this
;
abilities
.
insertLast
(
act
);
}
newCnt
+=
1
;
}
}
for
(
uint
i
=
newCnt
;
i
<
prevCnt
;
++
i
)
abilities
[
i
].
remove
();
int
y
=
62
;
if
(
newCnt
>
1
)
y
=
40
;
abilities
.
length
=
newCnt
;
for
(
uint
i
=
0
,
cnt
=
abilities
.
length
;
i
<
cnt
;
++
i
)
{
auto
@
btn
=
abilities
[
i
];
btn
.
init
();
btn
.
position
=
vec2i
(
size
.
width
-
184
,
y
);
btn
.
size
=
vec2i
(
172
,
50
);
y
+=
50
;
}
}
void
addAbility
(
Ability
@
abl
,
uint
&
newCnt
)
{
}
void
draw
()
override
{
if
(
att
is
null
||
att
.
type
is
null
)
return
;
skin
.
draw
(
SS_EmpireBox
,
SF_Normal
,
AbsolutePosition
,
att
.
type
.
color
);
BaseGuiElement
::
draw
();
}
};
class
AttitudesTab
:
Tab
{
array
<
AttitudeBox
@
>
boxes
;
array
<
Attitude
>
attitudes
;
GuiButton
@
takeButton
;
GuiMarkupText
@
takeText
;
GuiSkinElement
@
header
;
GuiEmpire
@
portrait
;
GuiMarkupText
@
headerText
;
GuiPanel
@
panel
;
AttitudesTab
()
{
super
();
title
=
locale
::
ATTITUDES_TAB
;
@
panel
=
GuiPanel
(
this
,
Alignment
());
@
takeButton
=
GuiButton
(
panel
,
recti_area
(
0
,
0
,
280
,
60
));
@
takeText
=
GuiMarkupText
(
takeButton
,
Alignment
(
Left
,
Top
+
15
,
Right
,
Bottom
));
takeText
.
defaultFont
=
FT_Subtitle
;
@
header
=
GuiSkinElement
(
panel
,
Alignment
(
Left
+
0.5f
-
400
,
Top
+
16
,
Left
+
0.5f
+
400
,
Top
+
200
),
SS_Panel
);
@
portrait
=
GuiEmpire
(
header
,
Alignment
(
Left
+
4
,
Top
+
4
,
Left
+
180
,
Bottom
-
4
));
@
headerText
=
GuiMarkupText
(
header
,
Alignment
(
Left
+
188
,
Top
+
8
,
Right
-
8
,
Bottom
-
8
));
headerText
.
text
=
locale
::
ATT_HEADER_DESC
;
updateAbsolutePosition
();
}
Color
get_activeColor
()
{
return
Color
(
0x63ebdbff
);
}
Color
get_inactiveColor
()
{
return
Color
(
0x6dd6caff
);
}
Color
get_seperatorColor
()
{
return
Color
(
0x37837aff
);
}
TabCategory
get_category
()
{
return
TC_Attitudes
;
}
Sprite
get_icon
()
{
return
Sprite
(
material
::
TabAttitude
);
}
void
tick
(
double
time
)
override
{
if
(!
visible
)
return
;
if
(
playerEmpire
is
null
||
!
playerEmpire
.
valid
)
{
attitudes
.
length
=
0
;
}
else
{
attitudes
.
syncFrom
(
playerEmpire
.
getAttitudes
());
@
portrait
.
empire
=
playerEmpire
;
}
uint
prevCnt
=
boxes
.
length
;
uint
newCnt
=
attitudes
.
length
;
for
(
uint
i
=
newCnt
;
i
<
prevCnt
;
++
i
)
boxes
[
i
].
remove
();
boxes
.
length
=
newCnt
;
for
(
uint
i
=
prevCnt
;
i
<
newCnt
;
++
i
)
@
boxes
[
i
]
=
AttitudeBox
(
panel
);
int
y
=
216
,
h
=
150
;
for
(
uint
i
=
0
;
i
<
newCnt
;
++
i
)
{
@
boxes
[
i
].
att
=
attitudes
[
i
];
@
boxes
[
i
].
alignment
=
Alignment
(
Left
+
0.1f
,
Top
+
y
,
Right
-
0.1f
,
Top
+
y
+
h
);
boxes
[
i
].
update
();
y
+=
h
+
8
;
}
bool
haveTakeable
=
false
;
for
(
uint
i
=
0
,
cnt
=
getAttitudeTypeCount
();
i
<
cnt
;
++
i
)
{
auto
@
att
=
getAttitudeType
(
i
);
if
(
att
.
canTake
(
playerEmpire
))
{
haveTakeable
=
true
;
break
;
}
}
if
(
haveTakeable
)
{
takeButton
.
visible
=
true
;
int
extraCost
=
playerEmpire
.
getNextAttitudeCost
();
takeButton
.
position
=
vec2i
((
size
.
width
-
takeButton
.
size
.
width
)/
2
,
y
+
20
);
takeText
.
text
=
"[center]"
+
format
(
locale
::
ATT_TAKE_COST
,
toString
(
extraCost
))+
"[/center]"
;
}
else
{
takeButton
.
visible
=
false
;
}
if
(
prevCnt
!=
newCnt
)
updateAbsolutePosition
();
}
bool
onGuiEvent
(
const
GuiEvent
&
event
)
{
if
(
event
.
type
==
GUI_Clicked
)
{
if
(
event
.
caller
is
takeButton
)
{
TakeAttitudeOverlay
(
this
);
return
true
;
}
}
return
BaseGuiElement
::
onGuiEvent
(
event
);
}
void
draw
()
{
skin
.
draw
(
SS_DesignOverviewBG
,
SF_Normal
,
AbsolutePosition
,
Color
(
0x6dd6caff
));
BaseGuiElement
::
draw
();
}
};
class
GuiFlatButton
:
GuiButton
{
bool
taken
=
false
;
bool
available
=
false
;
GuiFlatButton
(
IGuiElement
@
ParentElement
,
const
recti
&
Rectangle
)
{
super
(
ParentElement
,
Rectangle
);
updateAbsolutePosition
();
navigable
=
true
;
style
=
SS_NULL
;
}
void
draw
()
{
if
(!
available
)
{
if
(
flags
&
SF_Active
!=
0
)
drawRectangle
(
AbsolutePosition
,
color
.
interpolate
(
Color
(
0x00000080
),
0.6
));
else
drawRectangle
(
AbsolutePosition
,
Color
(
0x00000040
));
}
else
{
if
(
flags
&
SF_Active
!=
0
)
drawRectangle
(
AbsolutePosition
,
color
);
else
drawRectangle
(
AbsolutePosition
,
Color
(
0x00000060
));
}
if
(
flags
&
SF_Hovered
!=
0
)
drawRectangle
(
AbsolutePosition
,
Color
(
0xffffff20
));
if
(!
available
)
{
drawLine
(
AbsolutePosition
.
topLeft
+
vec2i
(
size
.
width
/5, size.height/
2
+
3
),
AbsolutePosition
.
botRight
-
vec2i
(
size
.
width
/5, size.height/
2
-
3
),
Color
(
0x444444ff
),
3
);
}
if
(
taken
)
{
vec2i
tl
=
AbsolutePosition
.
topLeft
+
vec2i
(
2
,
2
);
vec2i
br
=
AbsolutePosition
.
botRight
-
vec2i
(
2
,
2
);
drawLine
(
vec2i
(
tl
.
x
,
tl
.
y
),
vec2i
(
br
.
x
,
tl
.
y
),
color
.
interpolate
(
colors
::
White
,
0.2
),
3
);
drawLine
(
vec2i
(
tl
.
x
,
tl
.
y
),
vec2i
(
tl
.
x
,
br
.
y
),
color
.
interpolate
(
colors
::
White
,
0.2
),
3
);
drawLine
(
vec2i
(
br
.
x
,
tl
.
y
),
vec2i
(
br
.
x
,
br
.
y
),
color
.
interpolate
(
colors
::
White
,
0.2
),
3
);
drawLine
(
vec2i
(
tl
.
x
,
br
.
y
),
vec2i
(
br
.
x
,
br
.
y
),
color
.
interpolate
(
colors
::
White
,
0.2
),
3
);
Color
acolor
=
color
;
acolor
.
a
=
0x50
;
drawRectangle
(
AbsolutePosition
,
acolor
);
}
GuiButton
::
draw
();
}
}
class
TakeAttitudeOverlay
:
GuiOverlay
{
GuiSkinElement
@
bg
;
GuiPanel
@
leftPanel
;
GuiPanel
@
rightPanel
;
GuiMarkupText
@
text
;
array
<
const
AttitudeType
@
>
attitudes
;
array
<
GuiButton
@
>
buttons
;
const
AttitudeType
@
selected
;
GuiButton
@
takeButton
;
GuiMarkupText
@
takeText
;
GuiMarkupText
@
takeCaption
;
TakeAttitudeOverlay
(
IGuiElement
@
parent
)
{
super
(
parent
);
closeSelf
=
false
;
for
(
uint
i
=
0
,
cnt
=
getAttitudeTypeCount
();
i
<
cnt
;
++
i
)
{
auto
@
att
=
getAttitudeType
(
i
);
/*if(att.canTake(playerEmpire))*/
attitudes
.
insertLast
(
att
);
}
attitudes
.
sortAsc
();
int
h
=
16
+
clamp
(
attitudes
.
length
/
2
*
50
,
370
,
600
)
+
50
;
@
bg
=
GuiSkinElement
(
this
,
Alignment
(
Left
+
0.5f
-
425
,
Top
+
0.5f
-(
h
/2), Left+0.5f+425, Top+0.5f+(h/
2
)),
SS_Panel
);
@
leftPanel
=
GuiPanel
(
bg
,
Alignment
(
Left
,
Top
,
Left
+
300
,
Bottom
-
50
));
@
rightPanel
=
GuiPanel
(
bg
,
Alignment
(
Left
+
300
,
Top
,
Right
,
Bottom
-
50
));
@
text
=
GuiMarkupText
(
rightPanel
,
recti_area
(
8
,
8
,
550
-
16
,
100
));
text
.
flexHeight
=
true
;
@
takeButton
=
GuiButton
(
bg
,
Alignment
(
Left
+
0.5f
-
140
,
Bottom
-
50
,
Left
+
0.5f
+
140
,
Bottom
-
8
));
@
takeText
=
GuiMarkupText
(
takeButton
,
Alignment
(
Left
,
Top
+
10
,
Right
,
Bottom
));
@
takeCaption
=
GuiMarkupText
(
bg
,
Alignment
(
Left
+
0.5f
-
140
,
Bottom
-
40
,
Left
+
0.5f
+
140
,
Bottom
-
8
));
takeCaption
.
defaultFont
=
FT_Bold
;
takeCaption
.
defaultColor
=
Color
(
0xaa8080ff
);
takeCaption
.
visible
=
false
;
int
y
=
8
;
uint
sel
=
uint
(-
1
);
for
(
uint
i
=
0
,
cnt
=
attitudes
.
length
;
i
<
cnt
;
++
i
)
{
int
x
=
8
;
if
(
i
%
2
!=
0
)
x
=
150
;
auto
@
att
=
attitudes
[
i
];
GuiFlatButton
btn
(
leftPanel
,
recti_area
(
x
,
y
+
2
,
140
,
46
));
btn
.
toggleButton
=
true
;
btn
.
pressed
=
(
i
==
0
);
btn
.
text
=
att
.
name
;
btn
.
color
=
att
.
color
.
interpolate
(
colors
::
Black
,
0.75
);
if
(
playerEmpire
.
hasAttitude
(
att
.
id
))
{
btn
.
taken
=
true
;
btn
.
available
=
true
;
btn
.
textColor
=
colors
::
White
;
}
else
if
(!
att
.
canTake
(
playerEmpire
))
{
btn
.
taken
=
false
;
btn
.
available
=
false
;
btn
.
textColor
=
Color
(
0x666666ff
);
}
else
{
btn
.
taken
=
false
;
btn
.
available
=
true
;
btn
.
textColor
=
colors
::
White
;
if
(
sel
==
uint
(-
1
))
sel
=
i
;
}
if
(
i
%
2
!=
0
)
y
+=
50
;
buttons
.
insertLast
(
btn
);
}
if
(
sel
==
uint
(-
1
))
sel
=
0
;
if
(
attitudes
.
length
!=
0
)
select
(
attitudes
[
sel
]);
updateAbsolutePosition
();
}
void
select
(
const
AttitudeType
@
type
)
{
for
(
uint
j
=
0
,
cnt
=
buttons
.
length
;
j
<
cnt
;
++
j
)
buttons
[
j
].
pressed
=
(
type
is
attitudes
[
j
]);
@
selected
=
type
;
string
desc
=
format
(
"[font=Medium][color=$1][stroke=#000]$2[/stroke][/color][/font]\n[vspace=6/]"
,
toString
(
type
.
color
.
interpolate
(
colors
::
White
,
0.1
)),
type
.
name
);
for
(
uint
i
=
0
,
cnt
=
type
.
levels
.
length
;
i
<
cnt
;
++
i
)
{
auto
@
lvl
=
type
.
levels
[
i
];
desc
+=
format
(
"[img=$4;32][color=#aaa][b]$1 $2:[/b][/color] [color=#888]$3[/color]\n"
,
locale
::
LEVEL
,
toString
(
i
+
1
),
format
(
type
.
progress
,
toString
(
lvl
.
threshold
,
0
)),
getSpriteDesc
(
lvl
.
icon
));
desc
+=
"[offset=20]"
+
lvl
.
description
+
"[/offset][/img]"
;
desc
+=
"\n[vspace=2/]"
;
}
takeButton
.
color
=
type
.
color
;
int
extraCost
=
playerEmpire
.
getNextAttitudeCost
();
takeText
.
text
=
"[center]"
+
format
(
locale
::
ATT_TAKE_ATT
,
type
.
name
,
toString
(
extraCost
))+
"[/center]"
;
takeButton
.
disabled
=
playerEmpire
.
Influence
<
extraCost
;
takeButton
.
visible
=
selected
.
canTake
(
playerEmpire
);
takeCaption
.
visible
=
!
takeButton
.
visible
;
if
(
takeCaption
.
visible
)
{
if
(
playerEmpire
.
hasAttitude
(
selected
.
id
))
takeCaption
.
text
=
"[center]"
+
locale
::
ATT_HAVE_ATTITUDE
+
"[/center]"
;
else
takeCaption
.
text
=
"[center]"
+
locale
::
ATT_CANNOT_ATTITUDE
+
"[/center]"
;
}
text
.
text
=
desc
;
rightPanel
.
updateAbsolutePosition
();
}
bool
onGuiEvent
(
const
GuiEvent
&
event
)
override
{
if
(
event
.
type
==
GUI_Clicked
)
{
for
(
uint
i
=
0
,
cnt
=
buttons
.
length
;
i
<
cnt
;
++
i
)
{
if
(
buttons
[
i
]
is
event
.
caller
)
{
select
(
attitudes
[
i
]);
return
true
;
}
}
if
(
event
.
caller
is
takeButton
)
{
playerEmpire
.
takeAttitude
(
selected
.
id
);
close
();
return
true
;
}
}
return
GuiOverlay
::
onGuiEvent
(
event
);
}
};
Tab
@
createAttitudesTab
()
{
return
AttitudesTab
();
}
void
resetTabs
()
{
for
(
uint
i
=
0
,
cnt
=
tabs
.
length
;
i
<
cnt
;
++
i
)
{
if
(
tabs
[
i
].
category
==
TC_Attitudes
)
browseTab
(
tabs
[
i
],
createAttitudesTab
());
}
}
void
postReload
(
Message
&
msg
)
{
resetTabs
();
}
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sat, Nov 14, 11:08 PM (14 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
137029
Default Alt Text
AttitudesTab.as (14 KB)
Attached To
Mode
R80 StarRuler2-Source
Attached
Detach File
Event Timeline